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 <QtScxml/private/qscxmlcompiler_p.h>
5#include <QtScxml/qscxmltabledata.h>
6#include "scxmlcppdumper.h"
7#include "qscxmlc.h"
8
9#include <QCoreApplication>
10#include <QCommandLineParser>
11#include <QFile>
12#include <QFileInfo>
13#include <QStringConverter>
14
15QT_BEGIN_NAMESPACE
16
17enum {
18 NoError = 0,
19 CommandLineArgumentsError = -1,
20 NoInputFilesError = -2,
21 CannotOpenInputFileError = -3,
22 ParseError = -4,
23 CannotOpenOutputHeaderFileError = -5,
24 CannotOpenOutputCppFileError = -6,
25 ScxmlVerificationError = -7,
26 NoTextCodecError = -8
27};
28
29int write(TranslationUnit *tu)
30{
31 QTextStream errs(stderr, QIODevice::WriteOnly);
32
33 QFile outH(tu->outHFileName);
34 if (!outH.open(flags: QFile::WriteOnly)) {
35 errs << QStringLiteral("Error: cannot open '%1': %2").arg(args: outH.fileName(), args: outH.errorString()) << Qt::endl;
36 return CannotOpenOutputHeaderFileError;
37 }
38
39 QFile outCpp(tu->outCppFileName);
40 if (!outCpp.open(flags: QFile::WriteOnly)) {
41 errs << QStringLiteral("Error: cannot open '%1': %2").arg(args: outCpp.fileName(), args: outCpp.errorString()) << Qt::endl;
42 return CannotOpenOutputCppFileError;
43 }
44
45 // Make sure it outputs UTF-8, as that is what C++ expects.
46 auto utf8 = QStringConverter::encodingForName(name: "UTF-8");
47 if (!utf8) {
48 errs << QStringLiteral("Error: cannot find a QStringConverter for generating UTF-8.");
49 return NoTextCodecError;
50 }
51
52 QTextStream h(&outH);
53 h.setEncoding(utf8.value());
54 h.setGenerateByteOrderMark(true);
55 QTextStream c(&outCpp);
56 c.setEncoding(utf8.value());
57 c.setGenerateByteOrderMark(true);
58 CppDumper dumper(h, c);
59 dumper.dump(unit: tu);
60 h.flush();
61 outH.close();
62 c.flush();
63 outCpp.close();
64 return NoError;
65}
66
67static void collectAllDocuments(DocumentModel::ScxmlDocument *doc,
68 QList<DocumentModel::ScxmlDocument *> *docs)
69{
70 docs->append(t: doc);
71 for (DocumentModel::ScxmlDocument *subDoc : std::as_const(t&: doc->allSubDocuments))
72 collectAllDocuments(doc: subDoc, docs);
73}
74
75int run(const QStringList &arguments)
76{
77 QCommandLineParser cmdParser;
78 QTextStream errs(stderr, QIODevice::WriteOnly);
79
80 cmdParser.addHelpOption();
81 cmdParser.addVersionOption();
82 cmdParser.setApplicationDescription(QCoreApplication::translate(context: "main",
83 key: "Compiles the given input.scxml file to a header and a cpp file."));
84
85 QCommandLineOption optionNamespace(QLatin1String("namespace"),
86 QCoreApplication::translate(context: "main", key: "Put generated code into <namespace>."),
87 QCoreApplication::translate(context: "main", key: "namespace"));
88 QCommandLineOption optionOutputBaseName(QStringList() << QLatin1String("o") << QLatin1String("output"),
89 QCoreApplication::translate(context: "main", key: "Generate <name>.h and <name>.cpp files."),
90 QCoreApplication::translate(context: "main", key: "name"));
91 QCommandLineOption optionOutputHeaderName(QLatin1String("header"),
92 QCoreApplication::translate(context: "main", key: "Generate <name> for the header file."),
93 QCoreApplication::translate(context: "main", key: "name"));
94 QCommandLineOption optionOutputSourceName(QLatin1String("impl"),
95 QCoreApplication::translate(context: "main", key: "Generate <name> for the source file."),
96 QCoreApplication::translate(context: "main", key: "name"));
97 QCommandLineOption optionClassName(QLatin1String("classname"),
98 QCoreApplication::translate(context: "main", key: "Generate <name> for state machine class name."),
99 QCoreApplication::translate(context: "main", key: "name"));
100 QCommandLineOption optionStateMethods(QLatin1String("statemethods"),
101 QCoreApplication::translate(context: "main", key: "Generate read and notify methods for states"));
102
103 cmdParser.addPositionalArgument(name: QLatin1String("input"),
104 description: QCoreApplication::translate(context: "main", key: "Input SCXML file."));
105 cmdParser.addOption(commandLineOption: optionNamespace);
106 cmdParser.addOption(commandLineOption: optionOutputBaseName);
107 cmdParser.addOption(commandLineOption: optionOutputHeaderName);
108 cmdParser.addOption(commandLineOption: optionOutputSourceName);
109 cmdParser.addOption(commandLineOption: optionClassName);
110 cmdParser.addOption(commandLineOption: optionStateMethods);
111
112 cmdParser.process(arguments);
113
114 const QStringList inputFiles = cmdParser.positionalArguments();
115
116 if (inputFiles.size() < 1) {
117 errs << QCoreApplication::translate(context: "main", key: "Error: no input file.") << Qt::endl;
118 cmdParser.showHelp(exitCode: NoInputFilesError);
119 }
120
121 if (inputFiles.size() > 1) {
122 errs << QCoreApplication::translate(context: "main", key: "Error: unexpected argument(s): %1")
123 .arg(a: inputFiles.mid(pos: 1).join(sep: QLatin1Char(' '))) << Qt::endl;
124 cmdParser.showHelp(exitCode: NoInputFilesError);
125 }
126
127 const QString scxmlFileName = inputFiles.at(i: 0);
128
129 TranslationUnit options;
130 options.stateMethods = cmdParser.isSet(option: optionStateMethods);
131 if (cmdParser.isSet(option: optionNamespace))
132 options.namespaceName = cmdParser.value(option: optionNamespace);
133 QString outFileName = cmdParser.value(option: optionOutputBaseName);
134 QString outHFileName = cmdParser.value(option: optionOutputHeaderName);
135 QString outCppFileName = cmdParser.value(option: optionOutputSourceName);
136 QString mainClassName = cmdParser.value(option: optionClassName);
137
138 if (outFileName.isEmpty())
139 outFileName = QFileInfo(scxmlFileName).baseName();
140 if (outHFileName.isEmpty())
141 outHFileName = outFileName + QLatin1String(".h");
142 if (outCppFileName.isEmpty())
143 outCppFileName = outFileName + QLatin1String(".cpp");
144
145 QFile file(scxmlFileName);
146 if (!file.open(flags: QFile::ReadOnly)) {
147 errs << QStringLiteral("Error: cannot open input file %1").arg(a: scxmlFileName);
148 return CannotOpenInputFileError;
149 }
150
151 QXmlStreamReader reader(&file);
152 QScxmlCompiler compiler(&reader);
153 compiler.setFileName(file.fileName());
154 compiler.compile();
155 if (!compiler.errors().isEmpty()) {
156 const auto errors = compiler.errors();
157 for (const QScxmlError &error : errors) {
158 errs << error.toString() << Qt::endl;
159 }
160 return ParseError;
161 }
162
163 auto mainDoc = QScxmlCompilerPrivate::get(compiler: &compiler)->scxmlDocument();
164 if (mainDoc == nullptr) {
165 Q_ASSERT(!compiler.errors().isEmpty());
166 const auto errors = compiler.errors();
167 for (const QScxmlError &error : errors) {
168 errs << error.toString() << Qt::endl;
169 }
170 return ScxmlVerificationError;
171 }
172
173 if (mainClassName.isEmpty())
174 mainClassName = mainDoc->root->name;
175 if (mainClassName.isEmpty()) {
176 mainClassName = QFileInfo(scxmlFileName).fileName();
177 int dot = mainClassName.lastIndexOf(c: QLatin1Char('.'));
178 if (dot != -1)
179 mainClassName = mainClassName.left(n: dot);
180 }
181
182 QList<DocumentModel::ScxmlDocument *> docs;
183 collectAllDocuments(doc: mainDoc, docs: &docs);
184
185 TranslationUnit tu = options;
186 tu.allDocuments = docs;
187 tu.scxmlFileName = QFileInfo(file).fileName();
188 tu.mainDocument = mainDoc;
189 tu.outHFileName = outHFileName;
190 tu.outCppFileName = outCppFileName;
191 tu.classnameForDocument.insert(key: mainDoc, value: mainClassName);
192
193 docs.pop_front();
194
195 for (DocumentModel::ScxmlDocument *doc : std::as_const(t&: docs)) {
196 auto name = doc->root->name;
197 auto prefix = name;
198 if (name.isEmpty()) {
199 prefix = QStringLiteral("%1_StateMachine").arg(a: mainClassName);
200 name = prefix;
201 }
202
203 int counter = 1;
204 while (tu.classnameForDocument.key(value: name) != nullptr)
205 name = QStringLiteral("%1_%2").arg(a: prefix).arg(a: ++counter);
206
207 tu.classnameForDocument.insert(key: doc, value: name);
208 }
209
210 return write(tu: &tu);
211}
212
213QT_END_NAMESPACE
214

source code of qtscxml/tools/qscxmlc/qscxmlc.cpp