1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "scxmlcppdumper.h" |
5 | #include "generator.h" |
6 | |
7 | #include <QtScxml/private/qscxmlexecutablecontent_p.h> |
8 | |
9 | #include <QtCore/qfileinfo.h> |
10 | #include <QtCore/qbuffer.h> |
11 | #include <QtCore/qfile.h> |
12 | #include <QtCore/qresource.h> |
13 | |
14 | #include <algorithm> |
15 | #include <functional> |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | using namespace QScxmlInternal; |
20 | |
21 | namespace { |
22 | |
23 | static const QString = QString::fromLatin1( |
24 | ba: "//\n" |
25 | "// Statemachine code from reading SCXML file '%1'\n" |
26 | "// Created by: The Qt SCXML Compiler version %2 (Qt %3)\n" |
27 | "// WARNING! All changes made in this file will be lost!\n" |
28 | "//\n" |
29 | ); |
30 | |
31 | static const QString revisionCheck = QString::fromLatin1( |
32 | ba: "#if !defined(Q_QSCXMLC_OUTPUT_REVISION)\n" |
33 | "#error \"The header file '%1' doesn't include <qscxmltabledata.h>.\"\n" |
34 | "#elif Q_QSCXMLC_OUTPUT_REVISION != %2\n" |
35 | "#error \"This file was generated using the qscxmlc from %3. It\"\n" |
36 | "#error \"cannot be used with the include files from this version of Qt.\"\n" |
37 | "#error \"(The qscxmlc has changed too much.)\"\n" |
38 | "#endif\n" |
39 | ); |
40 | |
41 | QString cEscape(const QString &str) |
42 | { |
43 | QString res; |
44 | int lastI = 0; |
45 | for (int i = 0; i < str.size(); ++i) { |
46 | QChar c = str.at(i); |
47 | if (c < QLatin1Char(' ') || c == QLatin1Char('\\') || c == QLatin1Char('\"')) { |
48 | res.append(s: str.mid(position: lastI, n: i - lastI)); |
49 | lastI = i + 1; |
50 | if (c == QLatin1Char('\\')) { |
51 | res.append(s: QLatin1String("\\\\" )); |
52 | } else if (c == QLatin1Char('\"')) { |
53 | res.append(s: QLatin1String("\\\"" )); |
54 | } else if (c == QLatin1Char('\n')) { |
55 | res.append(s: QLatin1String("\\n" )); |
56 | } else if (c == QLatin1Char('\r')) { |
57 | res.append(s: QLatin1String("\\r" )); |
58 | } else { |
59 | char buf[6]; |
60 | ushort cc = c.unicode(); |
61 | buf[0] = '\\'; |
62 | buf[1] = 'u'; |
63 | for (int i = 0; i < 4; ++i) { |
64 | ushort ccc = cc & 0xF; |
65 | buf[5 - i] = ccc <= 9 ? '0' + ccc : 'a' + ccc - 10; |
66 | cc >>= 4; |
67 | } |
68 | res.append(s: QLatin1String(&buf[0], 6)); |
69 | } |
70 | } |
71 | } |
72 | if (lastI != 0) { |
73 | res.append(s: str.mid(position: lastI)); |
74 | return res; |
75 | } |
76 | return str; |
77 | } |
78 | |
79 | typedef QHash<QString, QString> Replacements; |
80 | static void genTemplate(QTextStream &out, const QString &filename, const Replacements &replacements) |
81 | { |
82 | QResource file(filename); |
83 | if (!file.isValid()) { |
84 | qFatal(msg: "Unable to open template '%s'" , qPrintable(filename)); |
85 | } |
86 | Q_ASSERT(file.compressionAlgorithm() == QResource::NoCompression); |
87 | const QString data = QString::fromLatin1( |
88 | ba: QByteArray::fromRawData(data: reinterpret_cast<const char *>(file.data()), size: int(file.size())) |
89 | ); |
90 | const QStringView t { data }; |
91 | |
92 | int start = 0; |
93 | for (int openIdx = t.indexOf(QStringLiteral("${" ), from: start); openIdx >= 0; openIdx = |
94 | t.indexOf(QStringLiteral("${" ), from: start)) { |
95 | out << t.mid(pos: start, n: openIdx - start); |
96 | openIdx += 2; |
97 | const int closeIdx = t.indexOf(c: QLatin1Char('}'), from: openIdx); |
98 | Q_ASSERT(closeIdx >= openIdx); |
99 | QString key = t.mid(pos: openIdx, n: closeIdx - openIdx).toString(); |
100 | if (!replacements.contains(key)) { |
101 | qFatal(msg: "Replacing '%s' failed: no replacement found" , qPrintable(key)); |
102 | } |
103 | out << replacements.value(key); |
104 | start = closeIdx + 1; |
105 | } |
106 | out << t.mid(pos: start); |
107 | } |
108 | |
109 | static const char * = |
110 | "#include <QScxmlStateMachine>\n" |
111 | "#include <QString>\n" |
112 | "#include <QVariant>\n" |
113 | "\n" ; |
114 | |
115 | using namespace DocumentModel; |
116 | |
117 | QString createContainer(const QStringList &elements) |
118 | { |
119 | QString result; |
120 | if (elements.isEmpty()) { |
121 | result += QStringLiteral("{}" ); |
122 | } else { |
123 | result += QStringLiteral("{ " ) + elements.join(QStringLiteral(", " )) + QStringLiteral(" }" ); |
124 | } |
125 | return result; |
126 | } |
127 | |
128 | static void generateList(QString &out, std::function<QString(int)> next) |
129 | { |
130 | const int maxLineLength = 80; |
131 | QString line; |
132 | for (int i = 0; ; ++i) { |
133 | const QString nr = next(i); |
134 | if (nr.isNull()) |
135 | break; |
136 | |
137 | if (i != 0) |
138 | line += QLatin1Char(','); |
139 | |
140 | if (line.size() + nr.size() + 1 > maxLineLength) { |
141 | out += line + QLatin1Char('\n'); |
142 | line.clear(); |
143 | } else if (i != 0) { |
144 | line += QLatin1Char(' '); |
145 | } |
146 | line += nr; |
147 | } |
148 | if (!line.isEmpty()) |
149 | out += line; |
150 | } |
151 | |
152 | void generateTables(const GeneratedTableData &td, Replacements &replacements) |
153 | { |
154 | { // instructions |
155 | auto instr = td.theInstructions; |
156 | QString out; |
157 | generateList(out, next: [&instr](int idx) -> QString { |
158 | if (instr.isEmpty() && idx == 0) // prevent generation of empty array |
159 | return QStringLiteral("-1" ); |
160 | if (idx < instr.size()) |
161 | return QString::number(instr.at(i: idx)); |
162 | else |
163 | return QString(); |
164 | }); |
165 | replacements[QStringLiteral("theInstructions" )] = out; |
166 | } |
167 | |
168 | { // dataIds |
169 | auto dataIds = td.theDataNameIds; |
170 | QString out; |
171 | generateList(out, next: [&dataIds](int idx) -> QString { |
172 | if (dataIds.size() == 0 && idx == 0) // prevent generation of empty array |
173 | return QStringLiteral("-1" ); |
174 | if (idx < dataIds.size()) |
175 | return QString::number(dataIds[idx]); |
176 | else |
177 | return QString(); |
178 | }); |
179 | replacements[QStringLiteral("dataNameCount" )] = QString::number(dataIds.size()); |
180 | replacements[QStringLiteral("dataIds" )] = out; |
181 | } |
182 | |
183 | { // evaluators |
184 | auto evaluators = td.theEvaluators; |
185 | QString out; |
186 | generateList(out, next: [&evaluators](int idx) -> QString { |
187 | if (evaluators.isEmpty() && idx == 0) // prevent generation of empty array |
188 | return QStringLiteral("{ -1, -1 }" ); |
189 | if (idx >= evaluators.size()) |
190 | return QString(); |
191 | |
192 | const auto eval = evaluators.at(i: idx); |
193 | return QStringLiteral("{ %1, %2 }" ).arg(a: eval.expr).arg(a: eval.context); |
194 | }); |
195 | replacements[QStringLiteral("evaluatorCount" )] = QString::number(evaluators.size()); |
196 | replacements[QStringLiteral("evaluators" )] = out; |
197 | } |
198 | |
199 | { // assignments |
200 | auto assignments = td.theAssignments; |
201 | QString out; |
202 | generateList(out, next: [&assignments](int idx) -> QString { |
203 | if (assignments.isEmpty() && idx == 0) // prevent generation of empty array |
204 | return QStringLiteral("{ -1, -1, -1 }" ); |
205 | if (idx >= assignments.size()) |
206 | return QString(); |
207 | |
208 | auto assignment = assignments.at(i: idx); |
209 | return QStringLiteral("{ %1, %2, %3 }" ) |
210 | .arg(a: assignment.dest).arg(a: assignment.expr).arg(a: assignment.context); |
211 | }); |
212 | replacements[QStringLiteral("assignmentCount" )] = QString::number(assignments.size()); |
213 | replacements[QStringLiteral("assignments" )] = out; |
214 | } |
215 | |
216 | { // foreaches |
217 | auto foreaches = td.theForeaches; |
218 | QString out; |
219 | generateList(out, next: [&foreaches](int idx) -> QString { |
220 | if (foreaches.isEmpty() && idx == 0) // prevent generation of empty array |
221 | return QStringLiteral("{ -1, -1, -1, -1 }" ); |
222 | if (idx >= foreaches.size()) |
223 | return QString(); |
224 | |
225 | auto foreachItem = foreaches.at(i: idx); |
226 | return QStringLiteral("{ %1, %2, %3, %4 }" ).arg(a: foreachItem.array).arg(a: foreachItem.item) |
227 | .arg(a: foreachItem.index).arg(a: foreachItem.context); |
228 | }); |
229 | replacements[QStringLiteral("foreachCount" )] = QString::number(foreaches.size()); |
230 | replacements[QStringLiteral("foreaches" )] = out; |
231 | } |
232 | |
233 | { // strings |
234 | QString out; |
235 | auto strings = td.theStrings; |
236 | if (strings.isEmpty()) // prevent generation of empty array |
237 | strings.append(QStringLiteral("" )); |
238 | int ucharCount = 0; |
239 | generateList(out, next: [&ucharCount, &strings](int idx) -> QString { |
240 | if (idx >= strings.size()) |
241 | return QString(); |
242 | |
243 | const int length = strings.at(i: idx).size(); |
244 | const QString str = QStringLiteral("%1, %2" ).arg( |
245 | args: QString::number(ucharCount), args: QString::number(length)); |
246 | ucharCount += length + 1; |
247 | return str; |
248 | }); |
249 | replacements[QStringLiteral("stringCount" )] = QString::number(strings.size()); |
250 | replacements[QStringLiteral("strLits" )] = out; |
251 | |
252 | out.clear(); |
253 | for (int i = 0, ei = strings.size(); i < ei; ++i) { |
254 | const QString &string = strings.at(i); |
255 | QString result; |
256 | if (i != 0) |
257 | result += QLatin1Char('\n'); |
258 | for (int charPos = 0, eCharPos = string.size(); charPos < eCharPos; ++charPos) { |
259 | result.append(QStringLiteral("0x%1," ) |
260 | .arg(a: QString::number(string.at(i: charPos).unicode(), base: 16))); |
261 | } |
262 | result.append(QStringLiteral("0%1 // %2: %3" ) |
263 | .arg(args: QLatin1String(i < ei - 1 ? "," : "" ), args: QString::number(i), |
264 | args: cEscape(str: string))); |
265 | out += result; |
266 | } |
267 | replacements[QStringLiteral("uniLits" )] = out; |
268 | replacements[QStringLiteral("stringdataSize" )] = QString::number(ucharCount + 1); |
269 | } |
270 | } |
271 | |
272 | void generateCppDataModelEvaluators(const GeneratedTableData::DataModelInfo &info, |
273 | Replacements &replacements) |
274 | { |
275 | const QString switchStart = QStringLiteral(" switch (id) {\n" ); |
276 | const QString switchEnd = QStringLiteral(" default: break;\n }" ); |
277 | const QString unusedId = QStringLiteral(" Q_UNUSED(id);" ); |
278 | QString stringEvals; |
279 | if (!info.stringEvaluators.isEmpty()) { |
280 | stringEvals += switchStart; |
281 | for (auto it = info.stringEvaluators.constBegin(), eit = info.stringEvaluators.constEnd(); |
282 | it != eit; ++it) { |
283 | stringEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
284 | stringEvals += QStringLiteral(" return [this]()->QString{ return %1; }();\n" ) |
285 | .arg(a: it.value()); |
286 | } |
287 | stringEvals += switchEnd; |
288 | } else { |
289 | stringEvals += unusedId; |
290 | } |
291 | replacements[QStringLiteral("evaluateToStringCases" )] = stringEvals; |
292 | |
293 | QString boolEvals; |
294 | if (!info.boolEvaluators.isEmpty()) { |
295 | boolEvals += switchStart; |
296 | for (auto it = info.boolEvaluators.constBegin(), eit = info.boolEvaluators.constEnd(); |
297 | it != eit; ++it) { |
298 | boolEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
299 | boolEvals += QStringLiteral(" return [this]()->bool{ return %1; }();\n" ) |
300 | .arg(a: it.value()); |
301 | } |
302 | boolEvals += switchEnd; |
303 | } else { |
304 | boolEvals += unusedId; |
305 | } |
306 | replacements[QStringLiteral("evaluateToBoolCases" )] = boolEvals; |
307 | |
308 | QString variantEvals; |
309 | if (!info.variantEvaluators.isEmpty()) { |
310 | variantEvals += switchStart; |
311 | for (auto it = info.variantEvaluators.constBegin(), eit = info.variantEvaluators.constEnd(); |
312 | it != eit; ++it) { |
313 | variantEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
314 | variantEvals += QStringLiteral(" return [this]()->QVariant{ return %1; }();\n" ) |
315 | .arg(a: it.value()); |
316 | } |
317 | variantEvals += switchEnd; |
318 | } else { |
319 | variantEvals += unusedId; |
320 | } |
321 | replacements[QStringLiteral("evaluateToVariantCases" )] = variantEvals; |
322 | |
323 | QString voidEvals; |
324 | if (!info.voidEvaluators.isEmpty()) { |
325 | voidEvals = switchStart; |
326 | for (auto it = info.voidEvaluators.constBegin(), eit = info.voidEvaluators.constEnd(); |
327 | it != eit; ++it) { |
328 | voidEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
329 | voidEvals += QStringLiteral(" [this]()->void{ %1 }();\n" ).arg(a: it.value()); |
330 | voidEvals += QStringLiteral(" return;\n" ); |
331 | } |
332 | voidEvals += switchEnd; |
333 | } else { |
334 | voidEvals += unusedId; |
335 | } |
336 | replacements[QStringLiteral("evaluateToVoidCases" )] = voidEvals; |
337 | } |
338 | |
339 | int createFactoryId(QStringList &factories, const QString &className, |
340 | const QString &namespacePrefix, |
341 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
342 | const QList<QScxmlExecutableContent::StringId> &namelist, |
343 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters) |
344 | { |
345 | const int idx = factories.size(); |
346 | |
347 | QString line = QStringLiteral("case %1: return new " ).arg(a: QString::number(idx)); |
348 | if (invokeInfo.expr == QScxmlExecutableContent::NoEvaluator) { |
349 | line += QStringLiteral("QScxmlStaticScxmlServiceFactory(&%1::%2::staticMetaObject," ) |
350 | .arg(args: namespacePrefix, args: className); |
351 | } else { |
352 | line += QStringLiteral("QScxmlDynamicScxmlServiceFactory(" ); |
353 | } |
354 | line += QStringLiteral("invoke(%1, %2, %3, %4, %5, %6, %7), " ) |
355 | .arg(args: QString::number(invokeInfo.id), |
356 | args: QString::number(invokeInfo.prefix), |
357 | args: QString::number(invokeInfo.expr), |
358 | args: QString::number(invokeInfo.location), |
359 | args: QString::number(invokeInfo.context), |
360 | args: QString::number(invokeInfo.finalize)) |
361 | .arg(a: invokeInfo.autoforward ? QStringLiteral("true" ) : QStringLiteral("false" )); |
362 | { |
363 | QStringList l; |
364 | for (auto name : namelist) { |
365 | l.append(t: QString::number(name)); |
366 | } |
367 | line += QStringLiteral("%1, " ).arg(a: createContainer(elements: l)); |
368 | } |
369 | { |
370 | QStringList l; |
371 | for (const auto ¶meter : parameters) { |
372 | l += QStringLiteral("param(%1, %2, %3)" ) |
373 | .arg(args: QString::number(parameter.name), |
374 | args: QString::number(parameter.expr), |
375 | args: QString::number(parameter.location)); |
376 | } |
377 | line += QStringLiteral("%1);" ).arg(a: createContainer(elements: l)); |
378 | } |
379 | |
380 | factories.append(t: line); |
381 | return idx; |
382 | } |
383 | } // anonymous namespace |
384 | |
385 | void CppDumper::dump(TranslationUnit *unit) |
386 | { |
387 | Q_ASSERT(unit); |
388 | Q_ASSERT(unit->mainDocument); |
389 | |
390 | m_translationUnit = unit; |
391 | |
392 | QString namespacePrefix; |
393 | if (!m_translationUnit->namespaceName.isEmpty()) { |
394 | namespacePrefix = QStringLiteral("::%1" ).arg(a: m_translationUnit->namespaceName); |
395 | } |
396 | |
397 | QStringList classNames; |
398 | QList<GeneratedTableData> tables; |
399 | QList<GeneratedTableData::MetaDataInfo> metaDataInfos; |
400 | QList<GeneratedTableData::DataModelInfo> dataModelInfos; |
401 | QList<QStringList> factories; |
402 | auto docs = m_translationUnit->allDocuments; |
403 | tables.resize(size: docs.size()); |
404 | metaDataInfos.resize(size: tables.size()); |
405 | dataModelInfos.resize(size: tables.size()); |
406 | factories.resize(size: tables.size()); |
407 | auto classnameForDocument = m_translationUnit->classnameForDocument; |
408 | |
409 | for (int i = 0, ei = docs.size(); i != ei; ++i) { |
410 | auto doc = docs.at(i); |
411 | auto metaDataInfo = &metaDataInfos[i]; |
412 | GeneratedTableData::build(doc, table: &tables[i], metaDataInfo, dataModelInfo: &dataModelInfos[i], |
413 | func: [this, &factories, i, &classnameForDocument, &namespacePrefix]( |
414 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
415 | const QList<QScxmlExecutableContent::StringId> &names, |
416 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters, |
417 | const QSharedPointer<DocumentModel::ScxmlDocument> &content) -> int { |
418 | QString className; |
419 | if (invokeInfo.expr == QScxmlExecutableContent::NoEvaluator) { |
420 | className = mangleIdentifier(str: classnameForDocument.value(key: content.data())); |
421 | } |
422 | return createFactoryId(factories&: factories[i], className, namespacePrefix, |
423 | invokeInfo, namelist: names, parameters); |
424 | }); |
425 | classNames.append(t: mangleIdentifier(str: classnameForDocument.value(key: doc))); |
426 | } |
427 | |
428 | const QString = QFileInfo(m_translationUnit->outHFileName).fileName(); |
429 | const QString = headerName.toUpper() |
430 | .replace(before: QLatin1Char('.'), after: QLatin1Char('_')) |
431 | .replace(before: QLatin1Char('-'), after: QLatin1Char('_')); |
432 | const QStringList forwardDecls = classNames.mid(pos: 1); |
433 | writeHeaderStart(headerGuard, forwardDecls); |
434 | writeImplStart(); |
435 | |
436 | for (int i = 0, ei = tables.size(); i != ei; ++i) { |
437 | const GeneratedTableData &table = tables.at(i); |
438 | DocumentModel::ScxmlDocument *doc = docs.at(i); |
439 | writeClass(className: classNames.at(i), info: metaDataInfos.at(i)); |
440 | writeImplBody(table, className: classNames.at(i), doc, factory: factories.at(i), info: metaDataInfos.at(i)); |
441 | |
442 | if (doc->root->dataModel == DocumentModel::Scxml::CppDataModel) { |
443 | Replacements r; |
444 | r[QStringLiteral("datamodel" )] = doc->root->cppDataModelClassName; |
445 | generateCppDataModelEvaluators(info: dataModelInfos.at(i), replacements&: r); |
446 | genTemplate(out&: cpp, QStringLiteral(":/cppdatamodel.t" ), replacements: r); |
447 | } |
448 | } |
449 | |
450 | writeHeaderEnd(headerGuard, metatypeDecls: classNames); |
451 | writeImplEnd(); |
452 | } |
453 | |
454 | void CppDumper::(const QString &, const QStringList &forwardDecls) |
455 | { |
456 | h << doNotEditComment.arg(args&: m_translationUnit->scxmlFileName, |
457 | args: QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
458 | args: QString::fromLatin1(QT_VERSION_STR)) |
459 | << Qt::endl; |
460 | |
461 | h << QStringLiteral("#ifndef " ) << headerGuard << Qt::endl |
462 | << QStringLiteral("#define " ) << headerGuard << Qt::endl |
463 | << Qt::endl; |
464 | h << l(str: headerStart); |
465 | if (!m_translationUnit->namespaceName.isEmpty()) |
466 | h << l(str: "namespace " ) << m_translationUnit->namespaceName << l(str: " {" ) << Qt::endl << Qt::endl; |
467 | |
468 | if (!forwardDecls.isEmpty()) { |
469 | for (const QString &forwardDecl : forwardDecls) |
470 | h << QStringLiteral("class %1;" ).arg(a: forwardDecl) << Qt::endl; |
471 | h << Qt::endl; |
472 | } |
473 | } |
474 | |
475 | void CppDumper::writeClass(const QString &className, const GeneratedTableData::MetaDataInfo &info) |
476 | { |
477 | Replacements r; |
478 | r[QStringLiteral("classname" )] = className; |
479 | r[QStringLiteral("properties" )] = generatePropertyDecls(info); |
480 | if (m_translationUnit->stateMethods) { |
481 | r[QStringLiteral("accessors" )] = generateAccessorDecls(info); |
482 | r[QStringLiteral("signals" )] = generateSignalDecls(info); |
483 | } else { |
484 | r[QStringLiteral("accessors" )] = QString(); |
485 | r[QStringLiteral("signals" )] = QString(); |
486 | } |
487 | genTemplate(out&: h, QStringLiteral(":/decl.t" ), replacements: r); |
488 | } |
489 | |
490 | void CppDumper::(const QString &, const QStringList &metatypeDecls) |
491 | { |
492 | QString ns; |
493 | if (!m_translationUnit->namespaceName.isEmpty()) { |
494 | h << QStringLiteral("} // %1 namespace " ).arg(a: m_translationUnit->namespaceName) << Qt::endl |
495 | << Qt::endl; |
496 | ns = QStringLiteral("::%1" ).arg(a: m_translationUnit->namespaceName); |
497 | } |
498 | |
499 | for (const QString &name : metatypeDecls) { |
500 | h << QStringLiteral("Q_DECLARE_METATYPE(%1::%2*)" ).arg(args&: ns, args: name) << Qt::endl; |
501 | } |
502 | h << Qt::endl; |
503 | |
504 | h << QStringLiteral("#endif // " ) << headerGuard << Qt::endl; |
505 | } |
506 | |
507 | void CppDumper::writeImplStart() |
508 | { |
509 | cpp << doNotEditComment.arg(args&: m_translationUnit->scxmlFileName, |
510 | args: QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
511 | args: l(QT_VERSION_STR)) |
512 | << Qt::endl; |
513 | |
514 | QStringList includes; |
515 | for (DocumentModel::ScxmlDocument *doc : std::as_const(t&: m_translationUnit->allDocuments)) { |
516 | switch (doc->root->dataModel) { |
517 | case DocumentModel::Scxml::NullDataModel: |
518 | includes += l(str: "QScxmlNullDataModel" ); |
519 | break; |
520 | case DocumentModel::Scxml::JSDataModel: |
521 | includes += l(str: "QScxmlDataModel" ); |
522 | break; |
523 | case DocumentModel::Scxml::CppDataModel: |
524 | includes += doc->root->cppDataModelHeaderName; |
525 | break; |
526 | } |
527 | |
528 | } |
529 | includes.sort(); |
530 | includes.removeDuplicates(); |
531 | |
532 | QString = QFileInfo(m_translationUnit->outHFileName).fileName(); |
533 | cpp << l(str: "#include \"" ) << headerName << l(str: "\"" ) << Qt::endl; |
534 | cpp << Qt::endl |
535 | << QStringLiteral("#include <qscxmlinvokableservice.h>" ) << Qt::endl |
536 | << QStringLiteral("#include <qscxmltabledata.h>" ) << Qt::endl; |
537 | for (const QString &inc : std::as_const(t&: includes)) { |
538 | cpp << l(str: "#include <" ) << inc << l(str: ">" ) << Qt::endl; |
539 | } |
540 | cpp << Qt::endl |
541 | << revisionCheck.arg(args&: m_translationUnit->scxmlFileName, |
542 | args: QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
543 | args: QString::fromLatin1(QT_VERSION_STR)) |
544 | << Qt::endl; |
545 | if (!m_translationUnit->namespaceName.isEmpty()) |
546 | cpp << l(str: "namespace " ) << m_translationUnit->namespaceName << l(str: " {" ) << Qt::endl << Qt::endl; |
547 | } |
548 | |
549 | void CppDumper::writeImplBody(const GeneratedTableData &table, |
550 | const QString &className, |
551 | DocumentModel::ScxmlDocument *doc, |
552 | const QStringList &factory, |
553 | const GeneratedTableData::MetaDataInfo &info) |
554 | { |
555 | QString dataModelField, dataModelInitialization; |
556 | switch (doc->root->dataModel) { |
557 | case DocumentModel::Scxml::NullDataModel: |
558 | dataModelField = l(str: "QScxmlNullDataModel dataModel;" ); |
559 | dataModelInitialization = l(str: "stateMachine.setDataModel(&dataModel);" ); |
560 | break; |
561 | case DocumentModel::Scxml::JSDataModel: |
562 | dataModelField = l(str: "std::unique_ptr<QScxmlDataModel> dataModel;" ); |
563 | dataModelInitialization = l( |
564 | str: " dataModel.reset(QScxmlDataModel::createScxmlDataModel(QStringLiteral(\"ecmascriptdatamodel\")));\n" |
565 | " stateMachine.setDataModel(dataModel.get());\n" |
566 | ); |
567 | break; |
568 | case DocumentModel::Scxml::CppDataModel: |
569 | dataModelField = QStringLiteral("// Data model %1 is set from outside." ).arg( |
570 | a: doc->root->cppDataModelClassName); |
571 | dataModelInitialization = dataModelField; |
572 | break; |
573 | } |
574 | |
575 | QString name; |
576 | if (table.theName == -1) { |
577 | name = QStringLiteral("QString()" ); |
578 | } else { |
579 | name = QStringLiteral("string(%1)" ).arg(a: table.theName); |
580 | } |
581 | |
582 | QString serviceFactories; |
583 | if (factory.isEmpty()) { |
584 | serviceFactories = QStringLiteral(" Q_UNUSED(id);\n Q_UNREACHABLE();" ); |
585 | } else { |
586 | serviceFactories = QStringLiteral(" switch (id) {\n " ) |
587 | + factory.join(QStringLiteral("\n " )) |
588 | + QStringLiteral("\n default: Q_UNREACHABLE();\n }" ); |
589 | } |
590 | |
591 | |
592 | Replacements r; |
593 | r[QStringLiteral("classname" )] = className; |
594 | r[QStringLiteral("name" )] = name; |
595 | r[QStringLiteral("initialSetup" )] = QString::number(table.initialSetup()); |
596 | generateTables(td: table, replacements&: r); |
597 | r[QStringLiteral("dataModelField" )] = dataModelField; |
598 | r[QStringLiteral("dataModelInitialization" )] = dataModelInitialization; |
599 | r[QStringLiteral("theStateMachineTable" )] = |
600 | GeneratedTableData::toString(stateMachineTable: table.stateMachineTable()); |
601 | r[QStringLiteral("metaObject" )] = generateMetaObject(className, info); |
602 | r[QStringLiteral("serviceFactories" )] = serviceFactories; |
603 | genTemplate(out&: cpp, QStringLiteral(":/data.t" ), replacements: r); |
604 | } |
605 | |
606 | void CppDumper::writeImplEnd() |
607 | { |
608 | if (!m_translationUnit->namespaceName.isEmpty()) { |
609 | cpp << Qt::endl |
610 | << QStringLiteral("} // %1 namespace" ).arg(a: m_translationUnit->namespaceName) << Qt::endl; |
611 | } |
612 | } |
613 | |
614 | /*! |
615 | * \internal |
616 | * Mangles \a str to be a unique C++ identifier. Characters that are invalid for C++ identifiers |
617 | * are replaced by the pattern \c _0x<hex>_ where <hex> is the hexadecimal unicode |
618 | * representation of the character. As identifiers with leading underscores followed by either |
619 | * another underscore or a capital letter are reserved in C++, we also escape those, by escaping |
620 | * the first underscore, using the above method. |
621 | * |
622 | * We keep track of all identifiers we have used so far and if we find two different names that |
623 | * map to the same mangled identifier by the above method, we append underscores to the new one |
624 | * until the result is unique. |
625 | * |
626 | * \note |
627 | * Although C++11 allows for non-ascii (unicode) characters to be used in identifiers, |
628 | * many compilers forgot to read the spec and do not implement this. Some also do not |
629 | * implement C99 identifiers, because that is \e {at the implementation's discretion}. So, |
630 | * we are stuck with plain old boring identifiers. |
631 | */ |
632 | QString CppDumper::mangleIdentifier(const QString &str) |
633 | { |
634 | auto isNonDigit = [](QChar c) -> bool { |
635 | return (c >= QLatin1Char('a') && c <= QLatin1Char('z')) || |
636 | (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || |
637 | c == QLatin1Char('_'); |
638 | }; |
639 | |
640 | Q_ASSERT(!str.isEmpty()); |
641 | |
642 | QString mangled; |
643 | mangled.reserve(asize: str.size()); |
644 | |
645 | int i = 0; |
646 | if (str.startsWith(c: QLatin1Char('_')) && str.size() > 1) { |
647 | QChar ch = str.at(i: 1); |
648 | if (ch == QLatin1Char('_') |
649 | || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z'))) { |
650 | mangled += QLatin1String("_0x5f_" ); |
651 | ++i; |
652 | } |
653 | } |
654 | |
655 | for (int ei = str.size(); i != ei; ++i) { |
656 | auto c = str.at(i); |
657 | if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) { |
658 | mangled += c; |
659 | } else { |
660 | mangled += QLatin1String("_0x" ) + QString::number(c.unicode(), base: 16) + QLatin1Char('_'); |
661 | } |
662 | } |
663 | |
664 | while (true) { |
665 | auto it = m_mangledToOriginal.constFind(key: mangled); |
666 | if (it == m_mangledToOriginal.constEnd()) { |
667 | m_mangledToOriginal.insert(key: mangled, value: str); |
668 | break; |
669 | } else if (it.value() == str) { |
670 | break; |
671 | } |
672 | mangled += QStringLiteral("_" ); // append underscores until we get a unique name |
673 | } |
674 | |
675 | return mangled; |
676 | } |
677 | |
678 | QString CppDumper::generatePropertyDecls(const GeneratedTableData::MetaDataInfo &info) |
679 | { |
680 | QString decls; |
681 | |
682 | for (const QString &stateName : info.stateNames) { |
683 | if (stateName.isEmpty()) |
684 | continue; |
685 | |
686 | if (m_translationUnit->stateMethods) { |
687 | decls += QString::fromLatin1(ba: " Q_PROPERTY(bool %1 READ %2 NOTIFY %3)\n" ) |
688 | .arg(args: stateName, args: mangleIdentifier(str: stateName), |
689 | args: mangleIdentifier(str: stateName + QStringLiteral("Changed" ))); |
690 | } else { |
691 | decls += QString::fromLatin1(ba: " Q_PROPERTY(bool %1)\n" ).arg(a: stateName); |
692 | } |
693 | } |
694 | |
695 | return decls; |
696 | } |
697 | |
698 | QString CppDumper::generateAccessorDecls(const GeneratedTableData::MetaDataInfo &info) |
699 | { |
700 | QString decls; |
701 | |
702 | for (const QString &stateName : info.stateNames) { |
703 | if (!stateName.isEmpty()) |
704 | decls += QString::fromLatin1(ba: " bool %1() const;\n" ).arg(a: mangleIdentifier(str: stateName)); |
705 | } |
706 | |
707 | return decls; |
708 | } |
709 | |
710 | QString CppDumper::generateSignalDecls(const GeneratedTableData::MetaDataInfo &info) |
711 | { |
712 | QString decls; |
713 | |
714 | for (const QString &stateName : info.stateNames) { |
715 | if (!stateName.isEmpty()) { |
716 | decls += QString::fromLatin1(ba: " void %1(bool);\n" ) |
717 | .arg(a: mangleIdentifier(str: stateName + QStringLiteral("Changed" ))); |
718 | } |
719 | } |
720 | |
721 | return decls; |
722 | } |
723 | |
724 | QString CppDumper::generateMetaObject(const QString &className, |
725 | const GeneratedTableData::MetaDataInfo &info) |
726 | { |
727 | ClassDef classDef; |
728 | classDef.classname = className.toUtf8(); |
729 | classDef.qualified = classDef.classname; |
730 | classDef.superclassList << qMakePair(value1: QByteArray("QScxmlStateMachine" ), value2: FunctionDef::Public); |
731 | classDef.hasQObject = true; |
732 | FunctionDef constructor; |
733 | constructor.name = className.toUtf8(); |
734 | constructor.access = FunctionDef::Public; |
735 | constructor.isInvokable = true; |
736 | constructor.isConstructor = true; |
737 | |
738 | ArgumentDef arg; |
739 | arg.type.name = "QObject *" ; |
740 | arg.type.rawName = arg.type.name; |
741 | arg.normalizedType = arg.type.name; |
742 | arg.name = "parent" ; |
743 | arg.typeNameForCast = arg.type.name + "*" ; |
744 | constructor.arguments.append(t: arg); |
745 | classDef.constructorList.append(t: constructor); |
746 | |
747 | // stateNames: |
748 | int stateIdx = 0; |
749 | for (const QString &stateName : info.stateNames) { |
750 | if (stateName.isEmpty()) |
751 | continue; |
752 | |
753 | QByteArray utf8StateName = stateName.toUtf8(); |
754 | |
755 | FunctionDef signal; |
756 | signal.type.name = "void" ; |
757 | signal.type.rawName = signal.type.name; |
758 | signal.normalizedType = signal.type.name; |
759 | signal.name = utf8StateName + "Changed" ; |
760 | if (m_translationUnit->stateMethods) |
761 | signal.mangledName = mangleIdentifier(str: stateName + QStringLiteral("Changed" )).toUtf8(); |
762 | signal.access = FunctionDef::Public; |
763 | signal.isSignal = true; |
764 | signal.implementation = "QMetaObject::activate(%s, &staticMetaObject, %d, _a);" ; |
765 | |
766 | ArgumentDef arg; |
767 | arg.type.name = "bool" ; |
768 | arg.type.rawName = arg.type.name; |
769 | arg.normalizedType = arg.type.name; |
770 | arg.name = "active" ; |
771 | arg.typeNameForCast = arg.type.name + "*" ; |
772 | signal.arguments << arg; |
773 | classDef.signalList << signal; |
774 | |
775 | PropertyDef prop; |
776 | prop.name = stateName.toUtf8(); |
777 | if (m_translationUnit->stateMethods) |
778 | prop.mangledName = mangleIdentifier(str: stateName).toUtf8(); |
779 | prop.type = "bool" ; |
780 | prop.read = "isActive(" + QByteArray::number(stateIdx++) + ")" ; |
781 | prop.notify = utf8StateName + "Changed" ; |
782 | prop.notifyId = classDef.signalList.size() - 1; |
783 | prop.gspec = PropertyDef::ValueSpec; |
784 | prop.scriptable = "true" ; |
785 | classDef.propertyList << prop; |
786 | } |
787 | |
788 | // sub-statemachines: |
789 | QHash<QByteArray, QByteArray> knownQObjectClasses; |
790 | knownQObjectClasses.insert(key: QByteArray("QScxmlStateMachine" ), value: QByteArray()); |
791 | |
792 | QBuffer buf; |
793 | buf.open(openMode: QIODevice::WriteOnly); |
794 | Generator generator(&classDef, QList<QByteArray>(), knownQObjectClasses, |
795 | QHash<QByteArray, QByteArray>(), buf); |
796 | generator.generateCode(); |
797 | if (m_translationUnit->stateMethods) { |
798 | generator.generateAccessorDefs(); |
799 | generator.generateSignalDefs(); |
800 | } |
801 | buf.close(); |
802 | return QString::fromUtf8(ba: buf.buffer()); |
803 | } |
804 | |
805 | QT_END_NAMESPACE |
806 | |