blob: ce45ed0d1c01485d3eb1bcc6973ff3872b4d886f [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtScxml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtScxml/private/qscxmlcompiler_p.h>
#include <QtScxml/qscxmltabledata.h>
#include "scxmlcppdumper.h"
#include "qscxmlc.h"
#include <QCoreApplication>
#include <QCommandLineParser>
#include <QFile>
#include <QFileInfo>
#include <QTextCodec>
QT_BEGIN_NAMESPACE
enum {
NoError = 0,
CommandLineArgumentsError = -1,
NoInputFilesError = -2,
CannotOpenInputFileError = -3,
ParseError = -4,
CannotOpenOutputHeaderFileError = -5,
CannotOpenOutputCppFileError = -6,
ScxmlVerificationError = -7,
NoTextCodecError = -8
};
int write(TranslationUnit *tu)
{
QTextStream errs(stderr, QIODevice::WriteOnly);
QFile outH(tu->outHFileName);
if (!outH.open(QFile::WriteOnly)) {
errs << QStringLiteral("Error: cannot open '%1': %2").arg(outH.fileName(), outH.errorString()) << endl;
return CannotOpenOutputHeaderFileError;
}
QFile outCpp(tu->outCppFileName);
if (!outCpp.open(QFile::WriteOnly)) {
errs << QStringLiteral("Error: cannot open '%1': %2").arg(outCpp.fileName(), outCpp.errorString()) << endl;
return CannotOpenOutputCppFileError;
}
// Make sure it outputs UTF-8, as that is what C++ expects.
QTextCodec *utf8 = QTextCodec::codecForName("UTF-8");
if (!utf8) {
errs << QStringLiteral("Error: cannot find a QTextCodec for generating UTF-8.");
return NoTextCodecError;
}
QTextStream h(&outH);
h.setCodec(utf8);
h.setGenerateByteOrderMark(true);
QTextStream c(&outCpp);
c.setCodec(utf8);
c.setGenerateByteOrderMark(true);
CppDumper dumper(h, c);
dumper.dump(tu);
h.flush();
outH.close();
c.flush();
outCpp.close();
return NoError;
}
static void collectAllDocuments(DocumentModel::ScxmlDocument *doc,
QList<DocumentModel::ScxmlDocument *> *docs)
{
docs->append(doc);
for (DocumentModel::ScxmlDocument *subDoc : qAsConst(doc->allSubDocuments))
collectAllDocuments(subDoc, docs);
}
int run(const QStringList &arguments)
{
QCommandLineParser cmdParser;
QTextStream errs(stderr, QIODevice::WriteOnly);
cmdParser.addHelpOption();
cmdParser.addVersionOption();
cmdParser.setApplicationDescription(QCoreApplication::translate("main",
"Compiles the given input.scxml file to a header and a cpp file."));
QCommandLineOption optionNamespace(QLatin1String("namespace"),
QCoreApplication::translate("main", "Put generated code into <namespace>."),
QCoreApplication::translate("main", "namespace"));
QCommandLineOption optionOutputBaseName(QStringList() << QLatin1String("o") << QLatin1String("output"),
QCoreApplication::translate("main", "Generate <name>.h and <name>.cpp files."),
QCoreApplication::translate("main", "name"));
QCommandLineOption optionOutputHeaderName(QLatin1String("header"),
QCoreApplication::translate("main", "Generate <name> for the header file."),
QCoreApplication::translate("main", "name"));
QCommandLineOption optionOutputSourceName(QLatin1String("impl"),
QCoreApplication::translate("main", "Generate <name> for the source file."),
QCoreApplication::translate("main", "name"));
QCommandLineOption optionClassName(QLatin1String("classname"),
QCoreApplication::translate("main", "Generate <name> for state machine class name."),
QCoreApplication::translate("main", "name"));
QCommandLineOption optionStateMethods(QLatin1String("statemethods"),
QCoreApplication::translate("main", "Generate read and notify methods for states"));
cmdParser.addPositionalArgument(QLatin1String("input"),
QCoreApplication::translate("main", "Input SCXML file."));
cmdParser.addOption(optionNamespace);
cmdParser.addOption(optionOutputBaseName);
cmdParser.addOption(optionOutputHeaderName);
cmdParser.addOption(optionOutputSourceName);
cmdParser.addOption(optionClassName);
cmdParser.addOption(optionStateMethods);
cmdParser.process(arguments);
const QStringList inputFiles = cmdParser.positionalArguments();
if (inputFiles.count() < 1) {
errs << QCoreApplication::translate("main", "Error: no input file.") << endl;
cmdParser.showHelp(NoInputFilesError);
}
if (inputFiles.count() > 1) {
errs << QCoreApplication::translate("main", "Error: unexpected argument(s): %1")
.arg(inputFiles.mid(1).join(QLatin1Char(' '))) << endl;
cmdParser.showHelp(NoInputFilesError);
}
const QString scxmlFileName = inputFiles.at(0);
TranslationUnit options;
options.stateMethods = cmdParser.isSet(optionStateMethods);
if (cmdParser.isSet(optionNamespace))
options.namespaceName = cmdParser.value(optionNamespace);
QString outFileName = cmdParser.value(optionOutputBaseName);
QString outHFileName = cmdParser.value(optionOutputHeaderName);
QString outCppFileName = cmdParser.value(optionOutputSourceName);
QString mainClassName = cmdParser.value(optionClassName);
if (outFileName.isEmpty())
outFileName = QFileInfo(scxmlFileName).baseName();
if (outHFileName.isEmpty())
outHFileName = outFileName + QLatin1String(".h");
if (outCppFileName.isEmpty())
outCppFileName = outFileName + QLatin1String(".cpp");
QFile file(scxmlFileName);
if (!file.open(QFile::ReadOnly)) {
errs << QStringLiteral("Error: cannot open input file %1").arg(scxmlFileName);
return CannotOpenInputFileError;
}
QXmlStreamReader reader(&file);
QScxmlCompiler compiler(&reader);
compiler.setFileName(file.fileName());
compiler.compile();
if (!compiler.errors().isEmpty()) {
const auto errors = compiler.errors();
for (const QScxmlError &error : errors) {
errs << error.toString() << endl;
}
return ParseError;
}
auto mainDoc = QScxmlCompilerPrivate::get(&compiler)->scxmlDocument();
if (mainDoc == nullptr) {
Q_ASSERT(!compiler.errors().isEmpty());
const auto errors = compiler.errors();
for (const QScxmlError &error : errors) {
errs << error.toString() << endl;
}
return ScxmlVerificationError;
}
if (mainClassName.isEmpty())
mainClassName = mainDoc->root->name;
if (mainClassName.isEmpty()) {
mainClassName = QFileInfo(scxmlFileName).fileName();
int dot = mainClassName.lastIndexOf(QLatin1Char('.'));
if (dot != -1)
mainClassName = mainClassName.left(dot);
}
QList<DocumentModel::ScxmlDocument *> docs;
collectAllDocuments(mainDoc, &docs);
TranslationUnit tu = options;
tu.allDocuments = docs;
tu.scxmlFileName = QFileInfo(file).fileName();
tu.mainDocument = mainDoc;
tu.outHFileName = outHFileName;
tu.outCppFileName = outCppFileName;
tu.classnameForDocument.insert(mainDoc, mainClassName);
docs.pop_front();
for (DocumentModel::ScxmlDocument *doc : qAsConst(docs)) {
auto name = doc->root->name;
auto prefix = name;
if (name.isEmpty()) {
prefix = QStringLiteral("%1_StateMachine").arg(mainClassName);
name = prefix;
}
int counter = 1;
while (tu.classnameForDocument.key(name) != nullptr)
name = QStringLiteral("%1_%2").arg(prefix).arg(++counter);
tu.classnameForDocument.insert(doc, name);
}
return write(&tu);
}
QT_END_NAMESPACE