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

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