diff options
293 files changed, 9798 insertions, 10808 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6b2abde --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +.tag export-subst +.gitattributes export-ignore +.gitignore export-ignore diff --git a/.qmake.conf b/.qmake.conf index 49570b2..0957f58 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -2,4 +2,4 @@ load(qt_build_config) CONFIG += qt_example_installs CONFIG += warning_clean -MODULE_VERSION = 5.7.1 +MODULE_VERSION = 5.8.0 @@ -0,0 +1 @@ +$Format:%H$ diff --git a/examples/scxml/calculator-common/doc/src/calculator-compiling.qdocinc b/examples/scxml/calculator-common/doc/src/calculator-compiling.qdocinc index 54fe26b..3e5982f 100644 --- a/examples/scxml/calculator-common/doc/src/calculator-compiling.qdocinc +++ b/examples/scxml/calculator-common/doc/src/calculator-compiling.qdocinc @@ -9,11 +9,8 @@ We then specify the state machine to compile: \skipto STATECHARTS - \printline calculator + \printuntil statemachine.scxml - We also tell qmake to run \c qscxmlc, which generates \e statemachine.h and - \e statemachine.cpp, and adds them to the \c HEADERS and \c SOURCES - variables for compilation: - - \skipto load - \printline qscxmlc + The Qt SCXML Compiler, \c qscxmlc, is run automatically to generate + \e statemachine.h and \e statemachine.cpp, and to add them to the \c HEADERS + and \c SOURCES variables for compilation. diff --git a/examples/scxml/calculator-common/statemachine.scxml b/examples/scxml/calculator-common/statemachine.scxml index 7971ee0..b1c6bb7 100644 --- a/examples/scxml/calculator-common/statemachine.scxml +++ b/examples/scxml/calculator-common/statemachine.scxml @@ -144,7 +144,7 @@ </transition> <transition event="DISPLAY.UPDATE"> <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> + <send event="updateDisplay"> <param name="display" expr="short_expr==''?res:short_expr"/> </send> </transition> diff --git a/examples/scxml/calculator-qml/calculator-qml.pro b/examples/scxml/calculator-qml/calculator-qml.pro index 6221576..c40242f 100644 --- a/examples/scxml/calculator-qml/calculator-qml.pro +++ b/examples/scxml/calculator-qml/calculator-qml.pro @@ -11,5 +11,3 @@ STATECHARTS = ../calculator-common/statemachine.scxml # install target.path = $$[QT_INSTALL_EXAMPLES]/scxml/calculator-qml INSTALLS += target - -load(qscxmlc) diff --git a/examples/scxml/calculator-qml/calculator-qml.qml b/examples/scxml/calculator-qml/calculator-qml.qml index 846589f..386549e 100644 --- a/examples/scxml/calculator-qml/calculator-qml.qml +++ b/examples/scxml/calculator-qml/calculator-qml.qml @@ -51,6 +51,7 @@ import CalculatorStateMachine 1.0 import QtQuick 2.5 import QtQuick.Window 2.0 +import QtScxml 5.8 Window { id: window @@ -61,9 +62,9 @@ Window { CalculatorStateMachine { id: statemachine running: true - onEventOccurred: { - if (event.name === "updateDisplay") - resultText.text = event.data.display; + EventConnection { + events: ["updateDisplay"] + onOccurred: resultText.text = event.data.display } } diff --git a/examples/scxml/calculator-widgets/calculator-widgets.pro b/examples/scxml/calculator-widgets/calculator-widgets.pro index 55880f1..238980b 100644 --- a/examples/scxml/calculator-widgets/calculator-widgets.pro +++ b/examples/scxml/calculator-widgets/calculator-widgets.pro @@ -17,5 +17,3 @@ FORMS += \ # install target.path = $$[QT_INSTALL_EXAMPLES]/scxml/calculator-widgets INSTALLS += target - -load(qscxmlc) diff --git a/examples/scxml/calculator-widgets/doc/src/calculator.qdoc b/examples/scxml/calculator-widgets/doc/src/calculator.qdoc index 5055005..9463b53 100644 --- a/examples/scxml/calculator-widgets/doc/src/calculator.qdoc +++ b/examples/scxml/calculator-widgets/doc/src/calculator.qdoc @@ -66,9 +66,16 @@ \printuntil digit2 \printuntil } - To be notified when a state machine sends out an event, we connect to the - corresponding signal: + The state machine can notify other code when events occur: - \skipto eventOccurred + \quotefromfile calculator-common/statemachine.scxml + \skipto transition event="DISPLAY.UPDATE + \printuntil </transition + + We connect to the \c updateDisplay event to display the data passed by + the events: + + \quotefromfile calculator-widgets/mainwindow.cpp + \skipto connectToEvent \printuntil }); */ diff --git a/examples/scxml/calculator-widgets/mainwindow.cpp b/examples/scxml/calculator-widgets/mainwindow.cpp index eacafaa..d8c77f7 100644 --- a/examples/scxml/calculator-widgets/mainwindow.cpp +++ b/examples/scxml/calculator-widgets/mainwindow.cpp @@ -115,11 +115,9 @@ MainWindow::MainWindow(QScxmlStateMachine *machine, QWidget *parent) : m_machine->submitEvent("C"); }); - connect(m_machine, &QScxmlStateMachine::eventOccurred, [this](const QScxmlEvent &event) { - if (event.name() == QLatin1String("updateDisplay")) { - const QString display = event.data().toMap().value("display").toString(); - ui->display->setText(display); - } + m_machine->connectToEvent(QLatin1String("updateDisplay"), this, [this](const QScxmlEvent &event) { + const QString display = event.data().toMap().value("display").toString(); + ui->display->setText(display); }); } diff --git a/examples/scxml/ftpclient/doc/images/ftpclient-statechart.png b/examples/scxml/ftpclient/doc/images/ftpclient-statechart.png Binary files differnew file mode 100644 index 0000000..48decb2 --- /dev/null +++ b/examples/scxml/ftpclient/doc/images/ftpclient-statechart.png diff --git a/examples/scxml/ftpclient/doc/src/ftpclient.qdoc b/examples/scxml/ftpclient/doc/src/ftpclient.qdoc new file mode 100644 index 0000000..b69994d --- /dev/null +++ b/examples/scxml/ftpclient/doc/src/ftpclient.qdoc @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example ftpclient + \title Qt SCXML FTP Client Example + \ingroup examples-qtscxml + + \brief Implements a simple FTP client using a state machine. + + \e {FTP Client} uses Qt SCXML to implement a FTP client that can communicate + with a FTP service by sending FTP control messages translated from state + machine events and by translating server replies into state machine + events. The data received from the FTP server is printed on the console. + + \l{RFC 959} specifies state charts for the command handling of the FTP + client. They can be easily translated into SCXML to benefit from SCXML + nested states. Connections between the client and server and data transfer + are implemented by using C++. In addition, Qt signals and slots are used. + + The state machine has the following states: + + \image ftpclient-statechart.png + + \list + \li \e I as the initial state. + \li \e B for sending commands. + \li \e S for success. + \li \e F for failure. + \li \e W for waiting for a reply. + \li \e P for supplying a password upon server request. + \endlist + + The state machine is specified in the \e simpleftp.scxml file and compiled + into the \c FtpClient class that implements the logic of the FTP protocol. + It reacts to user input and to replies from the control channel by changing + states and sending external events. In addition, we implement a + \c FtpControlChannel class and a \c FtpDataChannel class that handle TCP + sockets and servers and convert line endings. + + \include examples-run.qdocinc + + \section1 Compiling the State Machine + + We link against the Qt SCXML module by adding the following line to the + \e .pro file: + + \quotefromfile ftpclient/ftpclient.pro + \printuntil scxml + + We then specify the state machine to compile: + + \skipto STATECHARTS + \printuntil simpleftp.scxml + + The Qt SCXML Compiler, \c qscxmlc, is run automatically to generate + \e ftpclient.h and \e ftpclient.cpp, and to add them to the \c HEADERS + and \c SOURCES variables for compilation. + + \section1 Instantiating the State Machine + + We instantiate the generated \c FtpClient class, as well as the + \c FtpDataChannel and \c FtpControlChannel classes in the \e main.cpp file: + + \quotefromfile ftpclient/main.cpp + \skipto #include + \printuntil ftpdatachannel.h + \dots + \skipto int main + \printuntil { + \dots + \skipto QCoreApplication + \printuntil FtpControlChannel + \dots + + \section1 Communicating with an FTP Server + + We print all data retrieved from the server on the console: + + \skipto QObject::connect(&dataChannel + \printuntil } + + We translate server replies into state machine events: + + \skipto QObject::connect(&controlChannel + \printuntil } + + We translate commands from the state machine into FTP control messages: + + \skipto ftpClient.connectToEvent( + \printuntil } + + We send commands to log into the FTP server as an anonymous user, to + announce a port for the data connection, and to retrive a file: + + \skipto QList + \printuntil }); + + We specify that the FTP client should send the next command when entering + the \e B state: + + \skipto ftpClient.connectToState("B" + \printuntil })); + + We specify that the FTP client should send an empty string as a password if + the server asks for one: + + \skipto ftpClient.connectToState("P" + \printuntil } + + Finally, we connect to the FTP server specified as the first argument of + the method and retrieve the file specified as the second argument: + + \skipto controlChannel.connectToServer(server) + \printuntil } + + For example, the following invocation prints the specified file from the + specified server: \c {ftpclient <server> <file>}. +*/ diff --git a/examples/scxml/ftpclient/ftpclient.pro b/examples/scxml/ftpclient/ftpclient.pro new file mode 100644 index 0000000..3ba8adf --- /dev/null +++ b/examples/scxml/ftpclient/ftpclient.pro @@ -0,0 +1,18 @@ +QT = core scxml + +TARGET = ftpclient + +TEMPLATE = app +STATECHARTS += simpleftp.scxml + +SOURCES += \ + main.cpp \ + ftpcontrolchannel.cpp \ + ftpdatachannel.cpp + +HEADERS += \ + ftpcontrolchannel.h \ + ftpdatachannel.h + +target.path = $$[QT_INSTALL_EXAMPLES]/scxml/ftpclient +INSTALLS += target diff --git a/tests/auto/qscxmlc/data/invalidInvoke2.scxml b/examples/scxml/ftpclient/ftpcontrolchannel.cpp index 218474e..c37b6e5 100644 --- a/tests/auto/qscxmlc/data/invalidInvoke2.scxml +++ b/examples/scxml/ftpclient/ftpcontrolchannel.cpp @@ -1,5 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -49,31 +47,47 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> <content> - <scxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> +#include "ftpcontrolchannel.h" + +FtpControlChannel::FtpControlChannel(QObject *parent) : QObject(parent) +{ + connect(&m_socket, &QIODevice::readyRead, this, &FtpControlChannel::onReadyRead); + connect(&m_socket, &QAbstractSocket::disconnected, this, &FtpControlChannel::closed); + connect(&m_socket, &QAbstractSocket::connected, this, [this]() { + emit opened(m_socket.localAddress(), m_socket.localPort()); + }); +} + +void FtpControlChannel::connectToServer(const QString &server) +{ + m_socket.connectToHost(server, 21); +} + +void FtpControlChannel::command(const QByteArray &command, const QByteArray ¶ms) +{ + QByteArray sendData = command; + if (!params.isEmpty()) + sendData += " " + params; + m_socket.write(sendData + "\r\n"); +} + +void FtpControlChannel::onReadyRead() +{ + m_buffer.append(m_socket.readAll()); + int rn = -1; + while ((rn = m_buffer.indexOf("\r\n")) != -1) { + QByteArray received = m_buffer.mid(0, rn); + m_buffer = m_buffer.mid(rn + 2); + int space = received.indexOf(' '); + if (space != -1) { + int code = received.mid(0, space).toInt(); + if (code == 0) + emit info(received.mid(space + 1)); + else + emit reply(code, received.mid(space + 1)); + } else { + emit invalidReply(received); + } + } +} diff --git a/tests/auto/qscxmlc/data/invalidInvoke3.scxml b/examples/scxml/ftpclient/ftpcontrolchannel.h Binary files differindex 10e9130..ce8ad72 100644 --- a/tests/auto/qscxmlc/data/invalidInvoke3.scxml +++ b/examples/scxml/ftpclient/ftpcontrolchannel.h diff --git a/tests/auto/qscxmlc/data/invalidInvoke.scxml b/examples/scxml/ftpclient/ftpdatachannel.cpp index 5758c04..f0a7aa4 100644 --- a/tests/auto/qscxmlc/data/invalidInvoke.scxml +++ b/examples/scxml/ftpclient/ftpdatachannel.cpp @@ -1,5 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -49,31 +47,47 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> <content> - <scxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> +#include "ftpdatachannel.h" + +FtpDataChannel::FtpDataChannel(QObject *parent) : QObject(parent) +{ + connect(&m_server, &QTcpServer::newConnection, this, [this]() { + m_socket.reset(m_server.nextPendingConnection()); + connect(m_socket.data(), &QTcpSocket::readyRead, [this]() { + emit dataReceived(m_socket->readAll()); + }); + }); +} + +void FtpDataChannel::listen(const QHostAddress &address) +{ + m_server.listen(address); +} + +void FtpDataChannel::sendData(const QByteArray &data) +{ + if (m_socket) + m_socket->write(QByteArray(data).replace("\n", "\r\n")); +} + +void FtpDataChannel::close() +{ + if (m_socket) + m_socket->disconnectFromHost(); +} + +QString FtpDataChannel::portspec() const +{ + // Yes, this is a weird format, but say hello to FTP. + QString portSpec; + quint32 ipv4 = m_server.serverAddress().toIPv4Address(); + quint16 port = m_server.serverPort(); + portSpec += QString::number((ipv4 & 0xff000000) >> 24); + portSpec += ',' + QString::number((ipv4 & 0x00ff0000) >> 16); + portSpec += ',' + QString::number((ipv4 & 0x0000ff00) >> 8); + portSpec += ',' + QString::number(ipv4 & 0x000000ff); + portSpec += ',' + QString::number((port & 0xff00) >> 8); + portSpec += ',' + QString::number(port &0x00ff); + return portSpec; +} diff --git a/tests/auto/qscxmlc/data/invalidContent.scxml b/examples/scxml/ftpclient/ftpdatachannel.h index 387bf21..2ca211c 100644 --- a/tests/auto/qscxmlc/data/invalidContent.scxml +++ b/examples/scxml/ftpclient/ftpdatachannel.h @@ -1,5 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -49,31 +47,45 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <content /> <scxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> +#ifndef FTPDATACHANNEL_H +#define FTPDATACHANNEL_H + +#include <QObject> +#include <QTcpServer> +#include <QTcpSocket> +#include <QScopedPointer> + +class FtpDataChannel : public QObject +{ + Q_OBJECT +public: + explicit FtpDataChannel(QObject *parent = 0); + + // Listen on a local address. + void listen(const QHostAddress &address = QHostAddress::Any); + + // Send data over the socket. + void sendData(const QByteArray &data); + + // Close the connection. + void close(); + + // Retrieve the port specification to be announced on the control channel. + // Something like "a,b,c,d,xxx,yyy" where + // - a.b.c.d would be the IP address in decimal/dot notation and + // - xxx,yyy are the upper and lower 8 bits of the TCP port in decimal + // (This will only work if the local address we're listening on is actually meaningful) + QString portspec() const; + +signals: + + // The FTP server has sent some data. + void dataReceived(const QByteArray &data); + +private: + QTcpServer m_server; + QScopedPointer<QTcpSocket> m_socket; +}; + +#endif // FTPDATACHANNEL_H diff --git a/examples/scxml/ftpclient/main.cpp b/examples/scxml/ftpclient/main.cpp new file mode 100644 index 0000000..25102d2 --- /dev/null +++ b/examples/scxml/ftpclient/main.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "simpleftp.h" +#include "ftpcontrolchannel.h" +#include "ftpdatachannel.h" +#include <QCoreApplication> +#include <iostream> + +struct Command { + QString cmd; + QString args; +}; + +int main(int argc, char *argv[]) +{ + if (argc != 3) { + qDebug() << "Usage: ftpclient <server> <file>"; + return 1; + } + + QString server = QString::fromLocal8Bit(argv[1]); + QString file = QString::fromLocal8Bit(argv[2]); + + QCoreApplication app(argc, argv); + FtpClient ftpClient; + FtpDataChannel dataChannel; + FtpControlChannel controlChannel; + + // Print all data retrieved from the server on the console. + QObject::connect(&dataChannel, &FtpDataChannel::dataReceived, [](const QByteArray &data) { + std::cout << data.constData(); + }); + + // Translate server replies into state machine events. + QObject::connect(&controlChannel, &FtpControlChannel::reply, &ftpClient, + [&ftpClient](int code, const QString ¶meters) { + ftpClient.submitEvent(QString("reply.%1xx").arg(code / 100), parameters); + }); + + // Translate commands from the state machine into FTP control messages. + ftpClient.connectToEvent("submit.cmd", &controlChannel, + [&controlChannel](const QScxmlEvent &event) { + controlChannel.command(event.name().mid(11).toUtf8(), event.data().toByteArray()); + }); + + // Commands to be sent + QList<Command> commands({ + {"cmd.USER", "anonymous"},// login + {"cmd.PORT", ""}, // announce port for data connection, args added below. + {"cmd.RETR", file} // retrieve a file + }); + + // When entering "B" state, send the next command. + ftpClient.connectToState("B", QScxmlStateMachine::onEntry([&]() { + if (commands.isEmpty()) { + app.quit(); + return; + } + Command command = commands.takeFirst(); + qDebug() << "Posting command" << command.cmd << command.args; + ftpClient.submitEvent(command.cmd, command.args); + })); + + // If the server asks for a password, send one. + ftpClient.connectToState("P", QScxmlStateMachine::onEntry([&ftpClient]() { + qDebug() << "Sending password"; + ftpClient.submitEvent("cmd.PASS", QString()); + })); + + // Connect to our own local FTP server + controlChannel.connectToServer(server); + QObject::connect(&controlChannel, &FtpControlChannel::opened, + [&](const QHostAddress &address, int) { + dataChannel.listen(address); + commands[1].args = dataChannel.portspec(); + ftpClient.start(); + }); + + return app.exec(); +} diff --git a/examples/scxml/ftpclient/simpleftp.scxml b/examples/scxml/ftpclient/simpleftp.scxml new file mode 100644 index 0000000..79eb7b9 --- /dev/null +++ b/examples/scxml/ftpclient/simpleftp.scxml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/**************************************************************************** +** +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +--> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="FtpClient" + datamodel="ecmascript"> + <state id="G" initial="I"> + <transition event="reply" target="E"/> + <transition event="cmd" target="F"/> + + <state id="I"> + <transition event="reply.2xx" target="S"/> + </state> + + <state id="B"> + <transition event="cmd.DELE cmd.CWD cmd.CDUP cmd.HELP cmd.NOOP cmd.QUIT cmd.SYST + cmd.STAT cmd.RMD cmd.MKD cmd.PWD cmd.PORT" + target="W.general"/> + <transition event="cmd.APPE cmd.LIST cmd.NLST cmd.REIN cmd.RETR cmd.STOR cmd.STOU" + target="W.1xx"/> + <transition event="cmd.USER" target="W.user"/> + + <state id="S"/> + <state id="F"/> + </state> + + <state id="W"> + <onentry> + <send eventexpr=""submit." + _event.name"> + <content expr="_event.data"/> + </send> + </onentry> + + <transition event="reply.2xx" target="S"/> + <transition event="reply.4xx reply.5xx" target="F"/> + + <state id="W.1xx"> + <transition event="reply.1xx" target="W.transfer"/> + </state> + <state id="W.transfer"/> + <state id="W.general"/> + <state id="W.user"> + <transition event="reply.3xx" target="P"/> + </state> + <state id="W.login"/> + </state> + + <state id="P"> + <transition event="cmd.PASS" target="W.login"/> + </state> + </state> + + <final id="E"/> +</scxml> diff --git a/examples/scxml/invoke-common/MainView.qml b/examples/scxml/invoke-common/MainView.qml index 0f51ae5..8d8c3e3 100644 --- a/examples/scxml/invoke-common/MainView.qml +++ b/examples/scxml/invoke-common/MainView.qml @@ -50,11 +50,12 @@ import QtQuick 2.5 import QtQuick.Window 2.2 +import QtScxml 5.8 Window { id: window visible: true - property var stateMachine + property StateMachine stateMachine color: "black" width: 400 @@ -69,7 +70,7 @@ Window { text: "Go Nowhere" width: parent.width height: parent.height / 2 - onClicked: stateMachine.goNowhere() + onClicked: stateMachine.submitEvent("goNowhere") enabled: stateMachine.somewhere } @@ -79,7 +80,7 @@ Window { width: parent.width height: parent.height / 2 y: parent.height / 2 - onClicked: stateMachine.goSomewhere() + onClicked: stateMachine.submitEvent("goSomewhere") enabled: stateMachine.nowhere } } @@ -91,7 +92,14 @@ Window { x: parent.width / 2 width: parent.width / 2 height: parent.height - property var anywhere: stateMachine.anywhere + + InvokedServices { + id: services + stateMachine: window.stateMachine + } + + property var anywhere: services.children.anywhere ? services.children.anywhere.stateMachine + : undefined } } diff --git a/examples/scxml/invoke-common/SubView.qml b/examples/scxml/invoke-common/SubView.qml index 61cc723..2c374d5 100644 --- a/examples/scxml/invoke-common/SubView.qml +++ b/examples/scxml/invoke-common/SubView.qml @@ -58,7 +58,7 @@ Item { text: "Go There" width: parent.width / 2 height: parent.height - onClicked: anywhere.goThere() + onClicked: anywhere.submitEvent("goThere") } Button { @@ -68,6 +68,6 @@ Item { width: parent.width / 2 height: parent.height x: width - onClicked: anywhere.goHere() + onClicked: anywhere.submitEvent("goHere") } } diff --git a/examples/scxml/invoke-common/statemachine.scxml b/examples/scxml/invoke-common/statemachine.scxml index b9defeb..ad04cb3 100644 --- a/examples/scxml/invoke-common/statemachine.scxml +++ b/examples/scxml/invoke-common/statemachine.scxml @@ -50,7 +50,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" diff --git a/examples/scxml/invoke-dynamic/doc/src/invoke-dynamic.qdoc b/examples/scxml/invoke-dynamic/doc/src/invoke-dynamic.qdoc index 8c0f570..bff773a 100644 --- a/examples/scxml/invoke-dynamic/doc/src/invoke-dynamic.qdoc +++ b/examples/scxml/invoke-dynamic/doc/src/invoke-dynamic.qdoc @@ -47,7 +47,7 @@ \e Directions of type \e http://www.w3.org/TR/scxml/ to invoke: \quotefromfile invoke-common/statemachine.scxml - \skipto enable-qt-mode + \skipto scxml \printuntil \section1 Dynamically Loading the State Machine diff --git a/examples/scxml/invoke-dynamic/invoke-dynamic.qml b/examples/scxml/invoke-dynamic/invoke-dynamic.qml index 616928e..4ff85b4 100644 --- a/examples/scxml/invoke-dynamic/invoke-dynamic.qml +++ b/examples/scxml/invoke-dynamic/invoke-dynamic.qml @@ -48,13 +48,13 @@ ** ****************************************************************************/ -import QtScxml 5.7 as Scxml +import QtScxml 5.8 MainView { stateMachine: directions.stateMachine - Scxml.StateMachineLoader { + StateMachineLoader { id: directions - filename: "qrc:///statemachine.scxml" + source: "qrc:///statemachine.scxml" } } diff --git a/examples/scxml/invoke-static/doc/src/invoke-static.qdoc b/examples/scxml/invoke-static/doc/src/invoke-static.qdoc index d2ecc78..099b904 100644 --- a/examples/scxml/invoke-static/doc/src/invoke-static.qdoc +++ b/examples/scxml/invoke-static/doc/src/invoke-static.qdoc @@ -47,7 +47,7 @@ \e Directions of type \e http://www.w3.org/TR/scxml/ to invoke: \quotefromfile invoke-common/statemachine.scxml - \skipto enable-qt-mode + \skipto scxml \printuntil \section1 Compiling the State Machine @@ -64,11 +64,9 @@ \skipto STATECHARTS \printline statemachine - We also tell qmake to run \c qscxmlc, which generates \e statemachine.h and - \e statemachine.cpp, and adds them to the \c HEADERS and \c SOURCES - variables for compilation: - - \printuntil qscxmlc + The Qt SCXML Compiler, \c qscxmlc, is run automatically to generate + \e statemachine.h and \e statemachine.cpp, and to add them to the \c HEADERS + and \c SOURCES variables for compilation. \section1 Instantiating the State Machine diff --git a/examples/scxml/invoke-static/invoke-static.pro b/examples/scxml/invoke-static/invoke-static.pro index 86e0845..e2e206c 100644 --- a/examples/scxml/invoke-static/invoke-static.pro +++ b/examples/scxml/invoke-static/invoke-static.pro @@ -9,8 +9,6 @@ RESOURCES += invoke-static.qrc STATECHARTS = ../invoke-common/statemachine.scxml -load(qscxmlc) - target.path = $$[QT_INSTALL_EXAMPLES]/scxml/invoke-static INSTALLS += target diff --git a/examples/scxml/mediaplayer-common/Mediaplayer.qml b/examples/scxml/mediaplayer-common/Mediaplayer.qml index 4d793fa..fdeb9d2 100644 --- a/examples/scxml/mediaplayer-common/Mediaplayer.qml +++ b/examples/scxml/mediaplayer-common/Mediaplayer.qml @@ -50,12 +50,12 @@ import QtQuick 2.5 import QtQuick.Window 2.2 -import QtScxml 5.7 as Scxml +import QtScxml 5.8 Window { id: root - property QtObject stateMachine: scxmlLoader.stateMachine - property alias filename: scxmlLoader.filename + property StateMachine stateMachine: scxmlLoader.stateMachine + property alias source: scxmlLoader.source visible: true width: 750 @@ -111,27 +111,30 @@ Window { color: stateMachine.playing ? "green" : "red" } - Scxml.StateMachineLoader { + StateMachineLoader { id: scxmlLoader } - Connections { - target: stateMachine - onPlaybackStarted: { - var media = data.media - theText.text = "Playing '" + media + "'" - theLog.text = theLog.text + "\nplaybackStarted with data: " + JSON.stringify(data) - } - onPlaybackStopped: { - var media = data.media - theText.text = "Stopped '" + media + "'" - theLog.text = theLog.text + "\nplaybackStopped with data: " + JSON.stringify(data) + EventConnection { + stateMachine: root.stateMachine + events: ["playbackStarted", "playbackStopped"] + onOccurred: { + var media = event.data.media + if (event.name === "playbackStarted") { + theText.text = "Playing '" + media + "'" + theLog.text = theLog.text + "\nplaybackStarted with data: " + + JSON.stringify(event.data) + } else if (event.name === "playbackStopped") { + theText.text = "Stopped '" + media + "'" + theLog.text = theLog.text + "\nplaybackStopped with data: " + + JSON.stringify(event.data) + } } } function tap(idx) { var media = theModel.get(idx).media var data = { "media": media } - stateMachine.tap(data) + stateMachine.submitEvent("tap", data) } } diff --git a/examples/scxml/mediaplayer-common/doc/src/mediaplayer-compiling.qdocinc b/examples/scxml/mediaplayer-common/doc/src/mediaplayer-compiling.qdocinc index d4eb6ee..711a32f 100644 --- a/examples/scxml/mediaplayer-common/doc/src/mediaplayer-compiling.qdocinc +++ b/examples/scxml/mediaplayer-common/doc/src/mediaplayer-compiling.qdocinc @@ -12,9 +12,6 @@ \skipto STATECHARTS \printline scxml - We also tell qmake to run \c qscxmlc, which generates \e mediaplayer.h and - \e mediaplayer.cpp, and adds them to the \c HEADERS and \c SOURCES - variables for compilation: - - \skipto load - \printline qscxmlc + The Qt SCXML Compiler, \c qscxmlc, is run automatically to generate + \e statemachine.h and \e statemachine.cpp, and to add them to the \c HEADERS + and \c SOURCES variables for compilation. diff --git a/examples/scxml/mediaplayer-common/doc/src/mediaplayer-dynamic.qdocinc b/examples/scxml/mediaplayer-common/doc/src/mediaplayer-dynamic.qdocinc deleted file mode 100644 index 94062ad..0000000 --- a/examples/scxml/mediaplayer-common/doc/src/mediaplayer-dynamic.qdocinc +++ /dev/null @@ -1,15 +0,0 @@ - \section1 Dynamically Loading the State Machine - - We link against the Qt SCXML module by adding the following line to the - example \e .pro file: - - \quotefromfile mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.pro - \skipto QT - \printline scxml - - We dynamically create the state machine in - \e mediaplayer-common\Mediaplayer.qml: - - \quotefromfile mediaplayer-common/Mediaplayer.qml - \skipto import QtScxml - \printuntil scxmlLoader.filename diff --git a/examples/scxml/mediaplayer-common/doc/src/mediaplayer-ecmascript-data-model.qdocinc b/examples/scxml/mediaplayer-common/doc/src/mediaplayer-ecmascript-data-model.qdocinc index 31218ef..98b33c7 100644 --- a/examples/scxml/mediaplayer-common/doc/src/mediaplayer-ecmascript-data-model.qdocinc +++ b/examples/scxml/mediaplayer-common/doc/src/mediaplayer-ecmascript-data-model.qdocinc @@ -4,6 +4,6 @@ \c <scxml> element in \e mediaplayer-common/mediaplayer.scxml: \quotefromfile mediaplayer-common/mediaplayer.scxml - \skipto enable-qt-mode + \skipto scxml \printuntil </datamodel> diff --git a/examples/scxml/mediaplayer-common/doc/src/mediaplayer-widgets-connecting-to-states.qdocinc b/examples/scxml/mediaplayer-common/doc/src/mediaplayer-widgets-connecting-to-states.qdocinc new file mode 100644 index 0000000..207f47d --- /dev/null +++ b/examples/scxml/mediaplayer-common/doc/src/mediaplayer-widgets-connecting-to-states.qdocinc @@ -0,0 +1,16 @@ + \section1 Connecting to States + + The media player state machine will send out events when users tap a control + and when playback starts or stops, as specified in the SCXML file: + + \quotefromfile mediaplayer-common/mediaplayer.scxml + \skipto <state + \printuntil !== + \printuntil </state + + To be notified when a state machine sends out an event, we connect to the + corresponding signals: + + \quotefromfile mediaplayer-common/mainwindow.cpp + \skipto connectToEvent + \printuntil playbackStopped diff --git a/examples/scxml/mediaplayer-common/mainwindow.cpp b/examples/scxml/mediaplayer-common/mainwindow.cpp index e9a5767..b4d3760 100644 --- a/examples/scxml/mediaplayer-common/mainwindow.cpp +++ b/examples/scxml/mediaplayer-common/mainwindow.cpp @@ -52,10 +52,11 @@ #include "ui_mainwindow.h" #include <QStringListModel> +#include <QScxmlStateMachine> QT_USE_NAMESPACE -MainWindow::MainWindow(QWidget *parent) : +MainWindow::MainWindow(QScxmlStateMachine *stateMachine, QWidget *parent) : QWidget(parent), ui(new Ui::MainWindow) { @@ -66,11 +67,15 @@ MainWindow::MainWindow(QWidget *parent) : << QStringLiteral("song 3"), this); ui->mediaListView->setModel(model); - connect(ui->mediaListView, &QAbstractItemView::clicked, [model,this](const QModelIndex & index){ + connect(ui->mediaListView, &QAbstractItemView::clicked, + [model, stateMachine](const QModelIndex & index) { QVariantMap data; data.insert(QStringLiteral("media"), model->data(index, Qt::EditRole).toString()); - emit tap(data); + stateMachine->submitEvent("tap", data); }); + + stateMachine->connectToEvent("playbackStarted", this, &MainWindow::started); + stateMachine->connectToEvent("playbackStopped", this, &MainWindow::stopped); } MainWindow::~MainWindow() @@ -78,16 +83,16 @@ MainWindow::~MainWindow() delete ui; } -void MainWindow::started(const QVariant &data) +void MainWindow::started(const QScxmlEvent &event) { - QString media = data.toMap().value("media").toString(); + const QString media = event.data().toMap().value("media").toString(); ui->logText->appendPlainText(QStringLiteral("call on slot started with media '%1'").arg(media)); ui->statusLabel->setText(QStringLiteral("Playing %1").arg(media)); } -void MainWindow::stopped(const QVariant &data) +void MainWindow::stopped(const QScxmlEvent &event) { - QString media = data.toMap().value("media").toString(); + const QString media = event.data().toMap().value("media").toString(); ui->logText->appendPlainText(QStringLiteral("call on slot stopped with media '%1'").arg(media)); ui->statusLabel->setText(QStringLiteral("Stopped")); } diff --git a/examples/scxml/mediaplayer-common/mainwindow.h b/examples/scxml/mediaplayer-common/mainwindow.h index e9ed5fe..c89c269 100644 --- a/examples/scxml/mediaplayer-common/mainwindow.h +++ b/examples/scxml/mediaplayer-common/mainwindow.h @@ -57,6 +57,10 @@ QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } + +class QScxmlEvent; +class QScxmlStateMachine; + QT_END_NAMESPACE class MainWindow : public QWidget @@ -64,15 +68,12 @@ class MainWindow : public QWidget Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(QScxmlStateMachine *stateMachine, QWidget *parent = 0); ~MainWindow(); -signals: - void tap(const QVariant &data); - -public slots: - void started(const QVariant &data); - void stopped(const QVariant &data); +private slots: + void started(const QScxmlEvent &event); + void stopped(const QScxmlEvent &event); private: QT_PREPEND_NAMESPACE(Ui::MainWindow) *ui; diff --git a/examples/scxml/mediaplayer-common/mediaplayer.scxml b/examples/scxml/mediaplayer-common/mediaplayer.scxml index 025245a..ca68039 100644 --- a/examples/scxml/mediaplayer-common/mediaplayer.scxml +++ b/examples/scxml/mediaplayer-common/mediaplayer.scxml @@ -50,7 +50,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" @@ -76,13 +75,13 @@ <state id="playing"> <onentry> <assign location="media" expr="_event.data.media"/> - <send type="qt:signal" event="playbackStarted"> + <send event="playbackStarted"> <param name="media" expr="media"/> </send> </onentry> <onexit> - <send type="qt:signal" event="playbackStopped"> + <send event="playbackStopped"> <param name="media" expr="media"/> </send> </onexit> diff --git a/examples/scxml/mediaplayer-qml-cppdatamodel/doc/src/mediaplayer-qml-cppdatamodel.qdoc b/examples/scxml/mediaplayer-qml-cppdatamodel/doc/src/mediaplayer-qml-cppdatamodel.qdoc index 633922a..421e72e 100644 --- a/examples/scxml/mediaplayer-qml-cppdatamodel/doc/src/mediaplayer-qml-cppdatamodel.qdoc +++ b/examples/scxml/mediaplayer-qml-cppdatamodel/doc/src/mediaplayer-qml-cppdatamodel.qdoc @@ -50,7 +50,7 @@ \c <scxml> element in the SCXML file: \quotefromfile mediaplayer-qml-cppdatamodel/mediaplayer-cppdatamodel.scxml - \skipto enable-qt-mode + \skipto scxml \printuntil datamodel The format of the \e datamodel attribute is: diff --git a/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-cppdatamodel.scxml b/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-cppdatamodel.scxml index 3683c19..4ca810e 100644 --- a/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-cppdatamodel.scxml +++ b/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-cppdatamodel.scxml @@ -50,7 +50,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" @@ -67,13 +66,13 @@ <script> media = eventData().value(QStringLiteral("media")).toString(); </script> - <send type="qt:signal" event="playbackStarted"> + <send event="playbackStarted"> <param name="media" expr="media"/> </send> </onentry> <onexit> - <send type="qt:signal" event="playbackStopped"> + <send event="playbackStopped"> <param name="media" expr="media"/> </send> </onexit> diff --git a/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-qml-cppdatamodel.pro b/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-qml-cppdatamodel.pro index fae454d..2d21a37 100644 --- a/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-qml-cppdatamodel.pro +++ b/examples/scxml/mediaplayer-qml-cppdatamodel/mediaplayer-qml-cppdatamodel.pro @@ -12,8 +12,6 @@ RESOURCES += mediaplayer-qml-cppdatamodel.qrc STATECHARTS = mediaplayer-cppdatamodel.scxml -load(qscxmlc) - target.path = $$[QT_INSTALL_EXAMPLES]/scxml/mediaplayer-qml-cppdatamodel INSTALLS += target diff --git a/examples/scxml/mediaplayer-qml-dynamic/doc/src/mediaplayer-qml-dynamic.qdoc b/examples/scxml/mediaplayer-qml-dynamic/doc/src/mediaplayer-qml-dynamic.qdoc index 525974a..22d3ea2 100644 --- a/examples/scxml/mediaplayer-qml-dynamic/doc/src/mediaplayer-qml-dynamic.qdoc +++ b/examples/scxml/mediaplayer-qml-dynamic/doc/src/mediaplayer-qml-dynamic.qdoc @@ -44,5 +44,19 @@ \include mediaplayer-ecmascript-data-model.qdocinc - \include mediaplayer-dynamic.qdocinc + \section1 Dynamically Loading the State Machine + + We link against the Qt SCXML module by adding the following line to the + example \e .pro file: + + \quotefromfile mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.pro + \skipto QT + \printline scxml + + We dynamically create the state machine in + \e mediaplayer-common\Mediaplayer.qml: + + \quotefromfile mediaplayer-common/Mediaplayer.qml + \skipto import QtScxml + \printuntil scxmlLoader.source */ diff --git a/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.pro b/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.pro index 1af874f..f032bba 100644 --- a/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.pro +++ b/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.pro @@ -7,8 +7,6 @@ SOURCES += mediaplayer-qml-dynamic.cpp RESOURCES += mediaplayer-qml-dynamic.qrc -load(qscxmlc) - target.path = $$[QT_INSTALL_EXAMPLES]/scxml/mediaplayer-qml-dynamic INSTALLS += target diff --git a/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.qml b/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.qml index 3e936c3..864a76f 100644 --- a/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.qml +++ b/examples/scxml/mediaplayer-qml-dynamic/mediaplayer-qml-dynamic.qml @@ -49,5 +49,5 @@ ****************************************************************************/ Mediaplayer { - filename: "qrc:///mediaplayer.scxml" + source: "qrc:///mediaplayer.scxml" } diff --git a/examples/scxml/mediaplayer-qml-static/mediaplayer-qml-static.pro b/examples/scxml/mediaplayer-qml-static/mediaplayer-qml-static.pro index b6ca140..d8af563 100644 --- a/examples/scxml/mediaplayer-qml-static/mediaplayer-qml-static.pro +++ b/examples/scxml/mediaplayer-qml-static/mediaplayer-qml-static.pro @@ -9,8 +9,6 @@ RESOURCES += mediaplayer-qml-static.qrc STATECHARTS = ../mediaplayer-common/mediaplayer.scxml -load(qscxmlc) - target.path = $$[QT_INSTALL_EXAMPLES]/scxml/mediaplayer-qml-static INSTALLS += target diff --git a/examples/scxml/mediaplayer-widgets-dynamic/doc/src/mediaplayer-widgets-dynamic.qdoc b/examples/scxml/mediaplayer-widgets-dynamic/doc/src/mediaplayer-widgets-dynamic.qdoc index 6cc41df..59ec84f 100644 --- a/examples/scxml/mediaplayer-widgets-dynamic/doc/src/mediaplayer-widgets-dynamic.qdoc +++ b/examples/scxml/mediaplayer-widgets-dynamic/doc/src/mediaplayer-widgets-dynamic.qdoc @@ -44,5 +44,22 @@ \include mediaplayer-ecmascript-data-model.qdocinc - \include mediaplayer-dynamic.qdocinc + \section1 Dynamically Loading the State Machine + + We link against the Qt SCXML module by adding the following line to the + example \e .pro file: + + \quotefromfile mediaplayer-widgets-dynamic/mediaplayer-widgets-dynamic.pro + \skipto QT + \printuntil scxml + + \quotefromfile mediaplayer-widgets-dynamic/mediaplayer-widgets-dynamic.cpp + + We dynamically create and instantiate the state machine in + \e mediaplayer-wigdets-dynamic/mediaplayer-widgets-dynamic.cpp: + + \skipto mainwindow.h + \printuntil /^\}/ + + \include mediaplayer-widgets-connecting-to-states.qdocinc */ diff --git a/examples/scxml/mediaplayer-widgets-dynamic/mediaplayer-widgets-dynamic.cpp b/examples/scxml/mediaplayer-widgets-dynamic/mediaplayer-widgets-dynamic.cpp index eba0fba..457e4ee 100644 --- a/examples/scxml/mediaplayer-widgets-dynamic/mediaplayer-widgets-dynamic.cpp +++ b/examples/scxml/mediaplayer-widgets-dynamic/mediaplayer-widgets-dynamic.cpp @@ -58,16 +58,9 @@ int main(int argc, char **argv) QApplication app(argc, argv); auto machine = QScxmlStateMachine::fromFile(QStringLiteral(":mediaplayer.scxml")); - MainWindow mainWindow; + MainWindow mainWindow(machine); machine->setParent(&mainWindow); - QObject::connect(&mainWindow, SIGNAL(tap(const QVariant &)), - machine, SLOT(tap(const QVariant &))); - QObject::connect(machine, SIGNAL(playbackStarted(const QVariant &)), - &mainWindow, SLOT(started(const QVariant &))); - QObject::connect(machine, SIGNAL(playbackStopped(const QVariant &)), - &mainWindow, SLOT(stopped(const QVariant &))); - machine->start(); mainWindow.show(); return app.exec(); diff --git a/examples/scxml/mediaplayer-widgets-static/doc/src/mediaplayer-widgets-static.qdoc b/examples/scxml/mediaplayer-widgets-static/doc/src/mediaplayer-widgets-static.qdoc index 9d1ae39..dfa1230 100644 --- a/examples/scxml/mediaplayer-widgets-static/doc/src/mediaplayer-widgets-static.qdoc +++ b/examples/scxml/mediaplayer-widgets-static/doc/src/mediaplayer-widgets-static.qdoc @@ -55,9 +55,5 @@ \skipto mediaplayer.h \printuntil MainWindow mainWindow - To be notified when a state machine sends out an event, we connect to the - corresponding signals. The media player state machine will send out events - when users tap a control and when playback starts or stops: - - \printuntil MainWindow::stopped + \include mediaplayer-widgets-connecting-to-states.qdocinc */ diff --git a/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.cpp b/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.cpp index 1668eac..6a049d9 100644 --- a/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.cpp +++ b/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.cpp @@ -58,14 +58,7 @@ int main(int argc, char **argv) QApplication app(argc, argv); MediaPlayerStateMachine machine; - MainWindow mainWindow; - - QObject::connect(&mainWindow, &MainWindow::tap, - &machine, &MediaPlayerStateMachine::tap); - QObject::connect(&machine, &MediaPlayerStateMachine::playbackStarted, - &mainWindow, &MainWindow::started); - QObject::connect(&machine, &MediaPlayerStateMachine::playbackStopped, - &mainWindow, &MainWindow::stopped); + MainWindow mainWindow(&machine); machine.start(); mainWindow.show(); diff --git a/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.pro b/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.pro index af87491..89a7e4a 100644 --- a/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.pro +++ b/examples/scxml/mediaplayer-widgets-static/mediaplayer-widgets-static.pro @@ -17,5 +17,3 @@ HEADERS += \ # install target.path = $$[QT_INSTALL_EXAMPLES]/scxml/mediaplayer-widgets-static INSTALLS += target - -load(qscxmlc) diff --git a/examples/scxml/pinball/doc/src/pinball.qdoc b/examples/scxml/pinball/doc/src/pinball.qdoc index b3566eb..5cf9f1d 100644 --- a/examples/scxml/pinball/doc/src/pinball.qdoc +++ b/examples/scxml/pinball/doc/src/pinball.qdoc @@ -344,6 +344,15 @@ \printuntil updateLights \dots 20 \skipto /^\ {16}<\// + \printuntil updateLightsAccordingToLettersState + \dots 20 + \skipto /^\ {16}<\// + \printuntil turnOnLights + \dots 20 + \skipto /^\ {16}<\// + \printuntil turnOffLights + \dots 20 + \skipto /^\ {16}<\// \printuntil /^\ {12}<\// The \c lightImpulseGenerator contains two child states: @@ -405,39 +414,21 @@ the machine to activate the \c jackpotStateOn. \skipto updateLights - \printuntil rLetterOn - \dots 32 - \skipto /^\ {28}<\// - \printuntil aLetterOn - \dots 32 - \skipto /^\ {28}<\// - \printuntil zLetterOn - \dots 32 - \skipto /^\ {28}<\// - \printuntil yLetterOn - \dots 32 - \skipto /^\ {28}<\// - \printuntil rLetterOn - \dots 32 - \skipto /^\ {28}<\// - \printuntil aLetterOn - \dots 32 - \skipto /^\ {28}<\// - \printuntil zLetterOn - \dots 32 - \skipto /^\ {28}<\// - \printuntil yLetterOn - \dots 32 - \skipto /^\ {28}<\// + \printuntil /^\ {16}<\// + \printuntil updateLightsAccordingToLettersState + \printuntil /^\ {16}<\// + \printuntil turnOnLights + \printuntil /^\ {16}<\// + \printuntil turnOffLights \printuntil /^\ {16}<\// When we receive the \c updateLights event, we first want to send a - \c updateScore signal outside of the state machine. We pass - the current values of the \c highScore and \c score variables to the signal. - This signal is received by the C++ part. + \c updateScore event outside of the state machine. We pass + the current values of the \c highScore and \c score variables to the event. + This event is received by the C++ part. Next, depending on whether we are in \c jackpotStateOn or \c jackpotStateOff, - we send the \c turnOnJackpot or the \c turnOffJackpot signal, + we send the \c turnOnJackpot or the \c turnOffJackpot event, which instructs the \c guiControl state to transition to \c jackpotLightOn or \c jackpotLightOff, respectively. @@ -463,11 +454,11 @@ The class is declared in \e mainwindow.h. \quotefromfile pinball/mainwindow.h - \skipto Pinball + \skipto MainWindow \printuntil }; The \c MainWindow class holds the pointer to the - \c {Pinball *m_machine} which is the state machine + \c {QScxmlStateMachine *m_machine} which is the state machine class automatically generated by Qt out of SCMXL file and the pointer to the \c {Ui::MainWindow *m_ui} which describes the GUI part. It also declares two helper methods. @@ -478,7 +469,7 @@ The constructor of the \c MainWindow class instantiates the GUI part of the application - and stores the pointer to the passed \c Pinball state machine. + and stores the pointer to the passed \c QScxmlStateMachine. It also initializes the GUI part and glues the GUI part to the state machine by connecting their communication interfaces together. @@ -491,10 +482,9 @@ the widget is disabled. We do that for all lights, targets, and description labels. - We also connect to the \c eventOccurred() signal, propagated - by the state machine and intercept the \c updateScore - event sent from the SCXML document in order to update - the score displays with the values passed with the event. + We also intercept the \c updateScore event sent by the state machine, + in order to update the score displays with the values + passed with the event. The info about hitting any GUI target needs to be passed to the state machine and we do that by connecting diff --git a/examples/scxml/pinball/mainwindow.cpp b/examples/scxml/pinball/mainwindow.cpp index f2887f0..777de8a 100644 --- a/examples/scxml/pinball/mainwindow.cpp +++ b/examples/scxml/pinball/mainwindow.cpp @@ -52,11 +52,11 @@ #include "ui_mainwindow.h" #include <QStringListModel> -#include "pinball.h" +#include <QScxmlStateMachine> QT_USE_NAMESPACE -MainWindow::MainWindow(Pinball *machine, QWidget *parent) : +MainWindow::MainWindow(QScxmlStateMachine *machine, QWidget *parent) : QWidget(parent), m_ui(new Ui::MainWindow), m_machine(machine) @@ -89,8 +89,13 @@ MainWindow::MainWindow(Pinball *machine, QWidget *parent) : initAndConnect(QLatin1String("onState"), m_ui->ballOutButton); // datamodel update - connect(m_machine, SIGNAL(eventOccurred(const QScxmlEvent &)), - this, SLOT(eventOccurred(const QScxmlEvent &))); + m_machine->connectToEvent("updateScore", [this] (const QScxmlEvent &event) { + const QVariant data = event.data(); + const QString highScore = data.toMap().value("highScore").toString(); + m_ui->highScoreLabel->setText(highScore); + const QString score = data.toMap().value("score").toString(); + m_ui->scoreLabel->setText(score); + }); // gui interaction connect(m_ui->cButton, &QAbstractButton::clicked, @@ -124,21 +129,5 @@ MainWindow::~MainWindow() void MainWindow::initAndConnect(const QString &state, QWidget *widget) { widget->setEnabled(m_machine->isActive(state)); - m_machine->connectToState(state, widget, SLOT(setEnabled(bool))); + m_machine->connectToState(state, widget, &QWidget::setEnabled); } - -void MainWindow::eventOccurred(const QScxmlEvent &event) -{ - if (event.originType() != QLatin1String("qt:signal")) - return; - - if (event.name() != QLatin1String("updateScore")) - return; - - const QVariant data = event.data(); - const QString highScore = data.toMap().value("highScore").toString(); - m_ui->highScoreLabel->setText(highScore); - const QString score = data.toMap().value("score").toString(); - m_ui->scoreLabel->setText(score); -} - diff --git a/examples/scxml/pinball/mainwindow.h b/examples/scxml/pinball/mainwindow.h index 174248c..57dffe7 100644 --- a/examples/scxml/pinball/mainwindow.h +++ b/examples/scxml/pinball/mainwindow.h @@ -57,26 +57,22 @@ QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } -class QScxmlEvent; +class QScxmlStateMachine; QT_END_NAMESPACE -class Pinball; class MainWindow : public QWidget { Q_OBJECT public: - explicit MainWindow(Pinball *machine, QWidget *parent = 0); + explicit MainWindow(QScxmlStateMachine *machine, QWidget *parent = 0); ~MainWindow(); -private slots: - void eventOccurred(const QScxmlEvent &event); - private: void initAndConnect(const QString &state, QWidget *widget); QT_PREPEND_NAMESPACE(Ui::MainWindow) *m_ui; - Pinball *m_machine; + QScxmlStateMachine *m_machine; }; #endif // MAINWINDOW_H diff --git a/examples/scxml/pinball/pinball.pro b/examples/scxml/pinball/pinball.pro index 2cb002d..549010d 100644 --- a/examples/scxml/pinball/pinball.pro +++ b/examples/scxml/pinball/pinball.pro @@ -17,5 +17,3 @@ HEADERS += \ # install target.path = $$[QT_INSTALL_EXAMPLES]/scxml/pinball INSTALLS += target - -load(qscxmlc) diff --git a/examples/scxml/pinball/pinball.scxml b/examples/scxml/pinball/pinball.scxml index 1ec7487..022f202 100644 --- a/examples/scxml/pinball/pinball.scxml +++ b/examples/scxml/pinball/pinball.scxml @@ -50,7 +50,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: no --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="Pinball" datamodel="ecmascript"> <datamodel> @@ -265,7 +264,7 @@ </transition> <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> + <send event="updateScore"> <param name="highScore" expr="highScore"/> <param name="score" expr="score"/> </send> @@ -277,97 +276,77 @@ <if cond="In('lightImpulseOn')"> <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> + <raise event="turnOnLights"/> <raise event="turnOnHurry"/> <raise event="turnOnJackpot"/> <raise event="turnOnGameOver"/> <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> + <raise event="updateLightsAccordingToLettersState"/> <raise event="turnOffHurry"/> <raise event="turnOffGameOver"/> <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> + <raise event="turnOnLights"/> <raise event="turnOnHurry"/> <raise event="turnOffGameOver"/> </if> <else/> <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> + <raise event="turnOffLights"/> <raise event="turnOffHurry"/> <raise event="turnOffJackpot"/> <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> + <raise event="turnOffLights"/> <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> + <raise event="updateLightsAccordingToLettersState"/> </if> <raise event="turnOffHurry"/> <raise event="turnOffGameOver"/> </if> </transition> + + <transition event="updateLightsAccordingToLettersState"> + <if cond="In('cLetterOn')"> + <raise event="turnOnC"/> + <else/> + <raise event="turnOffC"/> + </if> + <if cond="In('rLetterOn')"> + <raise event="turnOnR"/> + <else/> + <raise event="turnOffR"/> + </if> + <if cond="In('aLetterOn')"> + <raise event="turnOnA"/> + <else/> + <raise event="turnOffA"/> + </if> + <if cond="In('zLetterOn')"> + <raise event="turnOnZ"/> + <else/> + <raise event="turnOffZ"/> + </if> + <if cond="In('yLetterOn')"> + <raise event="turnOnY"/> + <else/> + <raise event="turnOffY"/> + </if> + </transition> + + <transition event="turnOnLights"> + <raise event="turnOnC"/> + <raise event="turnOnR"/> + <raise event="turnOnA"/> + <raise event="turnOnZ"/> + <raise event="turnOnY"/> + </transition> + + <transition event="turnOffLights"> + <raise event="turnOffC"/> + <raise event="turnOffR"/> + <raise event="turnOffA"/> + <raise event="turnOffZ"/> + <raise event="turnOffY"/> + </transition> </state> </parallel> </parallel> diff --git a/examples/scxml/scxml.pro b/examples/scxml/scxml.pro index 7a93fd4..8da9db6 100644 --- a/examples/scxml/scxml.pro +++ b/examples/scxml/scxml.pro @@ -1,6 +1,5 @@ TEMPLATE = subdirs -!msvc{ qtHaveModule(widgets) { SUBDIRS += trafficlight-widgets-static SUBDIRS += trafficlight-widgets-dynamic @@ -8,12 +7,14 @@ qtHaveModule(widgets) { SUBDIRS += mediaplayer-widgets-dynamic SUBDIRS += calculator-widgets SUBDIRS += pinball + SUBDIRS += sudoku } qtHaveModule(qml) { SUBDIRS += calculator-qml SUBDIRS += trafficlight-qml-static SUBDIRS += trafficlight-qml-dynamic + SUBDIRS += trafficlight-qml-simple SUBDIRS += mediaplayer-qml-static SUBDIRS += mediaplayer-qml-dynamic @@ -21,4 +22,5 @@ qtHaveModule(qml) { SUBDIRS += invoke-static SUBDIRS += invoke-dynamic } -} + +SUBDIRS += ftpclient diff --git a/examples/scxml/sudoku/data/nearly-solved-sudoku.data b/examples/scxml/sudoku/data/nearly-solved-sudoku.data new file mode 100644 index 0000000..0e5c31f --- /dev/null +++ b/examples/scxml/sudoku/data/nearly-solved-sudoku.data @@ -0,0 +1,9 @@ +0, 3, 5, 2, 6, 9, 7, 8, 1 +6, 8, 2, 5, 7, 1, 4, 9, 3 +1, 9, 7, 8, 3, 4, 5, 6, 2 +8, 2, 6, 1, 9, 5, 3, 4, 7 +3, 7, 4, 6, 8, 2, 9, 1, 5 +9, 5, 1, 7, 4, 3, 6, 2, 8 +5, 1, 9, 3, 2, 6, 8, 7, 4 +2, 4, 8, 9, 5, 7, 1, 3, 6 +7, 6, 3, 4, 1, 8, 2, 5, 9 diff --git a/examples/scxml/sudoku/data/sudoku.data b/examples/scxml/sudoku/data/sudoku.data new file mode 100644 index 0000000..342fdf0 --- /dev/null +++ b/examples/scxml/sudoku/data/sudoku.data @@ -0,0 +1,9 @@ +0, 0, 0, 2, 6, 0, 7, 0, 1 +6, 8, 0, 0, 7, 0, 0, 9, 0 +1, 9, 0, 0, 0, 4, 5, 0, 0 +8, 2, 0, 1, 0, 0, 0, 4, 0 +0, 0, 4, 6, 0, 2, 9, 0, 0 +0, 5, 0, 0, 0, 3, 0, 2, 8 +0, 0, 9, 3, 0, 0, 0, 7, 4 +0, 4, 0, 0, 5, 0, 0, 3, 6 +7, 0, 3, 0, 1, 8, 0, 0, 0 diff --git a/examples/scxml/sudoku/doc/images/sudoku.png b/examples/scxml/sudoku/doc/images/sudoku.png Binary files differnew file mode 100644 index 0000000..ef6d113 --- /dev/null +++ b/examples/scxml/sudoku/doc/images/sudoku.png diff --git a/examples/scxml/sudoku/doc/src/sudoku.qdoc b/examples/scxml/sudoku/doc/src/sudoku.qdoc new file mode 100644 index 0000000..c3d8971 --- /dev/null +++ b/examples/scxml/sudoku/doc/src/sudoku.qdoc @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example sudoku + \title Qt SCXML Sudoku Example + \ingroup examples-qtscxml + \brief Presents the use of SCXML in a sudoku game. + + \e {Sudoku} demonstrates how an SCXML file may be used in a game. + + \include examples-run.qdocinc + + \section1 Sudoku Features + + \image sudoku.png Screenshot of the Sudoku example + + Our sudoku contains the following features: + \list + \li Initially and when the game ends, the sudoku + enters the \c idle state. In that state the players + can see if their last game finished successfully or not. + The state machine is then in one of two child states + of the \c idle state: \c solved or \c unsolved, + respectively. In the \c idle state the players + can also choose the sudoku grid they would like to solve. + The grid is disabled and the user interaction + is ignored. + \li After players click the \uicontrol Start button, the sudoku + enters the \c playing state and is ready for the user + interaction on the board. + \li When the game is in the \c playing state and the players + click the \uicontrol Stop button, the game ends + and enters the \c unsolved child state of the \c idle state. + If the players have solved the current puzzle successfully, + the game automatically ends and enters the \c solved child state + of the \c idle state indicating success. + \li The board consist of 81 buttons, laid out in a 9x9 grid. + The buttons with initial values given remain disabled + during the game. The players can only interact + with buttons initially empty. Each + click on the button increases its value by one. + \li During the game the \uicontrol Undo button + is available for the players' convenience. + \endlist + + \section1 SCXML Part: Internal Logic Description + + The \e sudoku.scxml file describes the internal structure of + the states the sudoku game can be in, defines the transitions + between states, and triggers the appropriate script + functions when the transitions take place. It also + communicates with the GUI part by sending events and listening + to the upcoming events and reacting to them. + + We use the ECMAScript data model: + + \quotefromfile sudoku/sudoku.scxml + \skipto scxml + \printuntil ecmascript + + We declare the following variables: + + \printuntil </datamodel> + + \table + \header + \li Variable + \li Description + \row + \li \c initState + \li Holds the initial state of the current game. It is a + two-dimensional array of 9x9 cells that contain initial sudoku + numbers. The value of zero means the cell is initially empty. + \row + \li \c currentState + \li Holds the current state of the game being played. It is + similar to the \c initState variable and initially + contains the same content. However, when the players start + entering the numbers into the empty cells, this variable + is being updated accordingly, while the \c initState variable + remains unchanged. + \row + \li \c undoStack + \li Holds the history of players' moves. It is a vector of + the cells' coordinates that were touched last. Each + new modification during a game adds a pair of + x and y coordinates to that vector. + \endtable + + The variables above are shared with the script helper functions + defined in the \c sudoku.js file: + + \printuntil sudoku.js + + We call some of the functions defined there + when taking transitions or in reaction to the + events sent by the GUI. + + All the possible states mentioned before are defined in a + root state \c game. + + \printuntil idle + \dots 12 + \skipto id="unsolved" + \printuntil playing + \dots 12 + \skipto /^\ {8}<\// + \printline state + \dots 8 + \skipto /^\ {4}<\// + \printline state + + When the sudoku example is started, the state + machine enters the \c game state and stays in this + state until the application exits. When entering this + state, we raise internally the \c restart event. + This event is also being raised whenever the players + change the current sudoku grid or when they start + the game by pressing the \uicontrol Start button. + We do not want to send it when they have finished + the current game because we still want to show the + filled grid from the last game play. So, this event + is being raised from three different contexts + and is captured internally once in a targetless + transition of the \c game state: + + \quotefromfile sudoku/sudoku.scxml + \skipto <transition event="restart"> + \printuntil </transition> + + When we catch the \c restart event, we call + a helper \c restart() script method, defined in the \c sudoku.js + file and raise internally an additional \c update event. + + \quotefromfile sudoku/sudoku.js + \skipto restart + \printuntil } + + The \c restart() function assigns the \c initState into the \c currentState + variable and clears the \c undoStack variable. + + The \c update event is raised internally whenever we want to notify the GUI + that the grid contents have been changed and that + the GUI should update itself according to the passed values. This event + is caught in another targetless transition of the \c game state: + + \quotefromfile sudoku/sudoku.scxml + \skipto <transition event="update"> + \printuntil </transition> + + We send the external event \c updateGUI, + which is being intercepted in the \l {cpp}{C++ code}. + The \c updateGUI event is equipped + with additional data, specified inside \c <param> + elements. We pass two parameters, which + are accessible externally through the + \c currentState and \c initState names. + The actual values passed for them + equal the datamodel's \c currentState + and \c initState variables, respectively, which + are specified by the \c expr attributes. + + \quotefromfile sudoku/sudoku.scxml + \skipto idle + \printuntil </state> + + When in \c idle state, we react to two + events, which may be sent by the GUI part: + \c start and \c setup. Whenever we receive the + \c start event, we just transition to the \c playing + state. When we receive the \c setup event, we + expect that the GUI part has sent us the new grid + to be solved. The grid's new initial state is expected + to be passed through the \c initState field of + \c _event.data. We assign the passed value to the + \c initState variable defined in our datamodel and + restart the grid's contents. + + \skipto playing + \printuntil </transition> + \dots 12 + \skipto </state> + \printline </state> + + Whenever we enter the \c playing state, we reset + the grid's contents since we could have been still + showing the contents from the previous game play. + In the \c playing state we react to possible + events sent from the GUI: \c tap, \c undo, and \c stop. + + The \c tap event is sent when the players + press one of the enabled sudoku cells. + This event is expected to contain additional data + specifying the cell's coordinates, which are passed + through the \c x and \c y fields of \c _event.data. + First, we check if the passed coordinates are valid + by invoking the \c isValidPosition() script function: + + \quotefromfile sudoku/sudoku.js + \skipto isValidPosition + \printuntil } + + We ensure the coordinates are neither negative nor bigger than our grid. + In addition, we check if the coordinates point to an + initially empty cell, since we can not modify + the cells initially given by the grid description. + + When we have ensured the passed coordinates are correct, + we call \c calculateCurrentState() script function: + + \quotefromfile sudoku/sudoku.js + \skipto calculateCurrentState + \printuntil } + + This function increments the value of the passed + grid's cell and adds the new move to the + undo stack history. + + Right after the \c calculateCurrentState() function + finishes its execution, we check whether the grid is + already solved by calling the \c isSolved() script function: + + \quotefromfile sudoku/sudoku.js + \skipto isOK + \printuntil return true + \printuntil } + \skipto isSolved + \printuntil return true + \printuntil } + + The \c isSolved() function returns \c true if the + grid is properly solved. Since we need to check + each row, each column, and each 3x3 square, we define + the \c isOK() helper function. This function takes + the vector of numbers and returns \c true if the passed vector + contains unique numbers and no number equals zero, meaning + there is no empty cell. The main loop of the \c isSolved() is invoked + nine times. In every iteration, we construct three vectors of numbers + representing a row, a column, and a square of the grid and call \c isOK() + for them. When all 27 vectors are OK, the grid is solved properly + and we return \c true. + + Coming back to our SCXML file, in case \c isSolved() + returns \c true, we raise the \c solved event internally. + The last instruction in case of a proper move is to raise + the \c update event, since we need to notify the GUI + about the grid's change. + + \quotefromfile sudoku/sudoku.scxml + \skipto id="playing" + \printline playing + \dots 12 + \skipto undo + \printuntil </state> + + When in the \c playing state, we also react to the \c undo + event sent from the GUI. In this case, we call the \c undo() + script function and notify the GUI about the need of an update. + + \quotefromfile sudoku/sudoku.js + \skipto function undo + \printuntil } + + The \c undo() function removes the last move from + the history, if there was any, and decrements the current value + for the cell described by the coordinates taken from this move. + + The \c playing state is also ready for the \c stop + event sent by the GUI when the players press the \uicontrol Stop + button. In this case, we simply activate the \c idle state. + + In addition, we intercept the \c solved event + sent internally and activate the \c solved state in this case. + + \target cpp + \section1 C++ Part: Constructing the GUI + + The C++ part of the application consists of a + \c MainWindow class which constructs the GUI and glues it with the SCXML part. + The class is declared in \e mainwindow.h. + + \quotefromfile sudoku/mainwindow.h + \skipto MainWindow + \printuntil }; + + The \c MainWindow class holds the pointer to the + \c {QScxmlStateMachine *m_machine}, which is the state machine + class automatically generated by Qt out of the \c sudoku.scxml file. + It also holds the pointers to some GUI elements. + + \quotefromfile sudoku/mainwindow.cpp + \skipto MainWindow + \printuntil { + + The constructor of the \c MainWindow class + instantiates the GUI part of the application + and stores the pointer to the passed state machine. + It also initializes the GUI part and glues the + GUI part to the state machine by connecting + their communication interfaces together. + + \skipto clicked + \printuntil }); + + First, we create 81 buttons and connect + their \c clicked signal to a lambda expression + that submits the \c tap event to the state machine + passing the button's coordinates. + + Later, we add some horizontal and vertical lines + to the grid in order to group buttons in 3x3 boxes. + + \skipto clicked + \printuntil }); + + We create the \uicontrol {Start / Stop} button and + connect its clicked signal to a lambda expression + which submits the \c stop or \c start event + depending on whether the machine is in \c playing state + or not, respectively. + + We create a label informing whether the grid is solved + or not, and an \uicontrol Undo button, which submits the + \c undo event whenever it is clicked. + + \skipto clicked + \printuntil }); + + Then we create a combobox that is filled with + grid names to be solved. These grids are + read from the \c :/data directory of the application + compiled-in resources. + + \skipto currentIndexChanged + \printuntil setInitialValues + + Whenever the players change the grid in the combobox, + we read the grid contents storing it in the + variant map under the \c initValues key as a + list of lists of int variants and we submit the + \c setup event to the state machine passing + the grid's contents. Initially, we read the first + available grid from the list and pass it directly + to the sudoku state machine as the initial grid. + + \skipto connectToState + \printline playing + \dots 8 + \skipto }); + \printuntil connectToEvent + \dots 8 + \skipto }); + \printline }); + + Later, we connect to the signals that are being sent + whenever the machine enters or leaves the \c playing + or \c solved states, and we update some GUI parts accordingly. + We also connect to the state machine's \c updateGUI event + and update all the buttons' values according to the passed cells' states. + + \quotefromfile sudoku/main.cpp + \skipto #include + \printuntil /\}$/ + + In the \c main() function in the \c main.cpp file, we instantiate the + \c app application object, \c Sudoku state machine, + and \c MainWindow GUI class. We start the state machine, + show the main window, and execute the application. +*/ diff --git a/tests/auto/qscxmlc/data/invalidRoot2.scxml b/examples/scxml/sudoku/main.cpp index 9d6cce0..0af8d72 100644 --- a/tests/auto/qscxmlc/data/invalidRoot2.scxml +++ b/examples/scxml/sudoku/main.cpp @@ -1,5 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -49,30 +47,20 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <content> - <scxmn name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"datamodel <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> +#include "sudoku.h" +#include "mainwindow.h" + +#include <QApplication> + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + Sudoku machine; + MainWindow mainWindow(&machine); + + machine.start(); + mainWindow.show(); + return app.exec(); +} diff --git a/examples/scxml/sudoku/mainwindow.cpp b/examples/scxml/sudoku/mainwindow.cpp new file mode 100644 index 0000000..3a13010 --- /dev/null +++ b/examples/scxml/sudoku/mainwindow.cpp @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" + +#include <QStringListModel> +#include <QScxmlStateMachine> +#include <QComboBox> +#include <QToolButton> +#include <QLabel> +#include <QGridLayout> +#include <QFile> +#include <QDir> +#include <QTextStream> + +static int Size = 9; + +QT_USE_NAMESPACE + +static QVariantList emptyRow() +{ + QVariantList row; + for (int i = 0; i < Size; i++) + row.append(QVariant(0)); + return row; +} + +static QVariantMap readSudoku(const QString &fileName) +{ + QFile input(fileName); + input.open(QIODevice::ReadOnly | QIODevice::Text); + QTextStream str(&input); + const QString data = str.readAll(); + + QVariantList initRowsVariant; + const QStringList rows = data.split(QLatin1Char('\n')); + for (int i = 0; i < Size; i++) { + if (i < rows.count()) { + QVariantList initRowVariant; + const QStringList row = rows.at(i).split(QLatin1Char(',')); + for (int j = 0; j < Size; j++) { + const int val = j < row.count() + ? row.at(j).toInt() % (Size + 1) : 0; + initRowVariant.append(val); + } + initRowsVariant.append(QVariant(initRowVariant)); + } else { + initRowsVariant.append(QVariant(emptyRow())); + } + } + + QVariantMap dataVariant; + dataVariant.insert(QStringLiteral("initState"), initRowsVariant); + + return dataVariant; +} + +MainWindow::MainWindow(QScxmlStateMachine *machine, QWidget *parent) : + QWidget(parent), + m_machine(machine) +{ + const QVector<QToolButton *> initVector(Size, nullptr); + m_buttons = QVector<QVector<QToolButton *> >(Size, initVector); + + QGridLayout *layout = new QGridLayout(this); + + for (int i = 0; i < Size; i++) { + for (int j = 0; j < Size; j++) { + QToolButton *button = new QToolButton(this); + button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(button, i + i / 3, j + j / 3); + m_buttons[i][j] = button; + connect(button, &QToolButton::clicked, [this, i, j] () { + QVariantMap data; + data.insert(QStringLiteral("x"), i); + data.insert(QStringLiteral("y"), j); + m_machine->submitEvent("tap", data); + }); + } + } + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 2; j++) { + QFrame *hFrame = new QFrame(this); + hFrame->setFrameShape(QFrame::HLine); + layout->addWidget(hFrame, 4 * j + 3, 4 * i, 1, 3); + + QFrame *vFrame = new QFrame(this); + vFrame->setFrameShape(QFrame::VLine); + layout->addWidget(vFrame, 4 * i, 4 * j + 3, 3, 1); + } + } + + m_startButton = new QToolButton(this); + m_startButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_startButton->setText(tr("Start")); + layout->addWidget(m_startButton, Size + 3, 0, 1, 3); + + connect(m_startButton, &QAbstractButton::clicked, + [this] { + if (m_machine->isActive("playing")) + m_machine->submitEvent("stop"); + else + m_machine->submitEvent("start"); + }); + + m_label = new QLabel(tr("unsolved")); + m_label->setAlignment(Qt::AlignCenter); + layout->addWidget(m_label, Size + 3, 4, 1, 3); + + m_undoButton = new QToolButton(this); + m_undoButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_undoButton->setText(tr("Undo")); + m_undoButton->setEnabled(false); + layout->addWidget(m_undoButton, Size + 3, 8, 1, 3); + + connect(m_undoButton, &QAbstractButton::clicked, + [this] { + m_machine->submitEvent("undo"); + }); + + m_chooser = new QComboBox(this); + layout->addWidget(m_chooser, Size + 4, 0, 1, 11); + + QDir dataDir(QLatin1String(":/data")); + QFileInfoList sudokuFiles = dataDir.entryInfoList(QStringList() << "*.data"); + foreach (const QFileInfo &sudokuFile, sudokuFiles) + m_chooser->addItem(sudokuFile.completeBaseName(), sudokuFile.absoluteFilePath()); + + connect(m_chooser, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), + [this] (int index) { + const QString sudokuFile = m_chooser->itemData(index).toString(); + const QVariantMap initValues = readSudoku(sudokuFile); + m_machine->submitEvent("setup", initValues); + }); + + const QVariantMap initValues = readSudoku(m_chooser->itemData(0).toString()); + m_machine->setInitialValues(initValues); + + m_machine->connectToState("playing", [this] (bool playing) { + if (playing) { + m_startButton->setText(tr("Stop")); + m_undoButton->setEnabled(true); + m_chooser->setEnabled(false); + } else { + m_startButton->setText(tr("Start")); + m_undoButton->setEnabled(false); + m_chooser->setEnabled(true); + } + }); + + m_machine->connectToState("solved", [this] (bool solved) { + if (solved) + m_label->setText(tr("SOLVED !!!")); + else + m_label->setText(tr("unsolved")); + }); + + m_machine->connectToEvent("updateGUI", [this] (const QScxmlEvent &event) { + const QVariant data = event.data(); + + const QVariantList currentRows = data.toMap().value("currentState").toList(); + for (int i = 0; i < currentRows.count(); i++) { + const QVariantList row = currentRows.at(i).toList(); + for (int j = 0; j < row.count(); j++) { + const int value = row.at(j).toInt(); + const QString text = value ? QString::number(value) : QString(); + m_buttons[i][j]->setText(text); + } + } + + const bool active = m_machine->isActive("playing"); + + const QVariantList initRows = data.toMap().value("initState").toList(); + for (int i = 0; i < initRows.count(); i++) { + const QVariantList row = initRows.at(i).toList(); + for (int j = 0; j < row.count(); j++) { + const int value = row.at(j).toInt(); + const bool enabled = !value && active; // enable only zeroes from initState + m_buttons[i][j]->setEnabled(enabled); + } + } + }); + + setLayout(layout); +} + +MainWindow::~MainWindow() +{ +} + diff --git a/tests/auto/qscxmlc/data/noContentInInvoke.scxml b/examples/scxml/sudoku/mainwindow.h index 961a71e..99a0f1b 100644 --- a/tests/auto/qscxmlc/data/noContentInInvoke.scxml +++ b/examples/scxml/sudoku/mainwindow.h @@ -1,5 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -49,31 +47,35 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <contenscxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QWidget> + +QT_BEGIN_NAMESPACE +class QToolButton; +class QScxmlStateMachine; +class QLabel; +class QComboBox; +QT_END_NAMESPACE + + +class MainWindow : public QWidget +{ + Q_OBJECT + +public: + explicit MainWindow(QScxmlStateMachine *machine, QWidget *parent = 0); + ~MainWindow(); + +private: + QScxmlStateMachine *m_machine; + QVector<QVector<QToolButton *> > m_buttons; + QToolButton *m_startButton; + QToolButton *m_undoButton; + QLabel *m_label; + QComboBox *m_chooser; +}; + +#endif // MAINWINDOW_H diff --git a/examples/scxml/sudoku/sudoku.js b/examples/scxml/sudoku/sudoku.js new file mode 100644 index 0000000..21a1ed8 --- /dev/null +++ b/examples/scxml/sudoku/sudoku.js @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +function restart() { + for (var i = 0; i < initState.length; i++) + currentState[i] = initState[i].slice(); + undoStack = []; +} + +function isValidPosition() { + var x = _event.data.x; + var y = _event.data.y; + if (x < 0 || x >= initState.length) + return false; + if (y < 0 || y >= initState.length) + return false; + if (initState[x][y] !== 0) + return false; + return true; +} + +function calculateCurrentState() { + if (isValidPosition() === false) + return; + var x = _event.data.x; + var y = _event.data.y; + var currentValue = currentState[x][y]; + if (currentValue === initState.length) + currentValue = 0; + else + currentValue += 1; + currentState[x][y] = currentValue; + undoStack.push([x, y]); +} + +function isOK(numbers) { + var temp = []; + for (var i = 0; i < numbers.length; i++) { + var currentValue = numbers[i]; + if (currentValue === 0) + return false; + if (temp.indexOf(currentValue) >= 0) + return false; + temp.push(currentValue); + } + return true; +} + +function isSolved() { + for (var i = 0; i < currentState.length; i++) { + if (!isOK(currentState[i])) + return false; + + var column = []; + var square = []; + for (var j = 0; j < currentState[i].length; j++) { + column.push(currentState[j][i]); + square.push(currentState[Math.floor(i / 3) * 3 + Math.floor(j / 3)] + [i % 3 * 3 + j % 3]); + } + + if (!isOK(column)) + return false; + if (!isOK(square)) + return false; + } + return true; +} + +function undo() { + if (!undoStack.length) + return; + + var lastMove = undoStack.pop(); + var x = lastMove[0]; + var y = lastMove[1]; + var currentValue = currentState[x][y]; + if (currentValue === 0) + currentValue = initState.length; + else + currentValue -= 1; + currentState[x][y] = currentValue; +} diff --git a/examples/scxml/sudoku/sudoku.pro b/examples/scxml/sudoku/sudoku.pro new file mode 100644 index 0000000..0853a5e --- /dev/null +++ b/examples/scxml/sudoku/sudoku.pro @@ -0,0 +1,19 @@ +QT += widgets scxml + +CONFIG += c++11 + +STATECHARTS = sudoku.scxml + +SOURCES += \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + mainwindow.h + +RESOURCES += \ + sudoku.qrc + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/scxml/sudoku +INSTALLS += target diff --git a/examples/scxml/sudoku/sudoku.qrc b/examples/scxml/sudoku/sudoku.qrc new file mode 100644 index 0000000..6a15532 --- /dev/null +++ b/examples/scxml/sudoku/sudoku.qrc @@ -0,0 +1,8 @@ +<RCC> + <qresource prefix="/"> + <file>sudoku.js</file> + <file>data/sudoku.data</file> + <file>data/nearly-solved-sudoku.data</file> + </qresource> +</RCC> + diff --git a/examples/scxml/sudoku/sudoku.scxml b/examples/scxml/sudoku/sudoku.scxml new file mode 100644 index 0000000..eb36283 --- /dev/null +++ b/examples/scxml/sudoku/sudoku.scxml @@ -0,0 +1,111 @@ +<?xml version="1.0" ?> +<!-- +/**************************************************************************** +** +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +--> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" + name="Sudoku" datamodel="ecmascript"> + <datamodel> + <data id="initState"/> + <data id="currentState" expr="[[]]"/> + <data id="undoStack"/> + </datamodel> + <script src="sudoku.js"/> + <state id="game"> + <onentry> + <raise event="restart"/> + </onentry> + <state id="idle"> + <transition event="start" target="playing"/> + <transition event="setup" target="unsolved"> + <assign location="initState" expr="_event.data.initState"/> + <raise event="restart"/> + </transition> + <state id="unsolved"/> + <state id="solved"/> + </state> + <state id="playing"> + <onentry> + <raise event="restart"/> + </onentry> + <transition event="tap"> + <if cond="isValidPosition()"> + <script> + calculateCurrentState(); + </script> + <if cond="isSolved()"> + <raise event="solved"/> + </if> + <raise event="update"/> + </if> + </transition> + <transition event="undo"> + <script> + undo(); + </script> + <raise event="update"/> + </transition> + <transition event="stop" target="idle"/> + <transition event="solved" target="solved"/> + </state> + <transition event="restart"> + <script> + restart(); + </script> + <raise event="update"/> + </transition> + <transition event="update"> + <send event="updateGUI"> + <param name="currentState" expr="currentState"/> + <param name="initState" expr="initState"/> + </send> + </transition> + </state> +</scxml> diff --git a/examples/scxml/trafficlight-common/Lights.ui.qml b/examples/scxml/trafficlight-common/Lights.ui.qml new file mode 100644 index 0000000..a385eae --- /dev/null +++ b/examples/scxml/trafficlight-common/Lights.ui.qml @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +import QtQuick 2.5 +import QtQuick.Window 2.2 +import TrafficLightStateMachine 1.0 + +Image { + id: lights + + property alias button: button + property TrafficLightStateMachine stateMachine + + source: "background.png" + + Column { + y: 40 + spacing: 27 + anchors.horizontalCenter: parent.horizontalCenter + + Image { + id: redLight + opacity: 0.2 + source: "red.png" + } + + Image { + id: yellowLight + opacity: 0.2 + source: "yellow.png" + } + + Image { + id: greenLight + opacity: 0.2 + source: "green.png" + } + } + + Button { + id: button + + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 20 + source: "pause.png" + } + + states: [ + State { + name: "Red" + when: stateMachine.red + + PropertyChanges { + target: redLight + opacity: 1 + } + }, + State { + name: "RedGoingGreen" + when: stateMachine.redGoingGreen + + PropertyChanges { + target: redLight + opacity: 1 + } + + PropertyChanges { + target: yellowLight + opacity: 1 + } + }, + State { + name: "Yellow" + when: stateMachine.yellow || stateMachine.blinking + + PropertyChanges { + target: yellowLight + opacity: 1 + } + }, + State { + name: "Green" + when: stateMachine.green + + PropertyChanges { + target: greenLight + opacity: 1 + } + } + ] +} diff --git a/examples/scxml/trafficlight-common/TrafficLight.qml b/examples/scxml/trafficlight-common/TrafficLight.qml index a23fca7..1da3726 100644 --- a/examples/scxml/trafficlight-common/TrafficLight.qml +++ b/examples/scxml/trafficlight-common/TrafficLight.qml @@ -50,65 +50,23 @@ import QtQuick 2.5 import QtQuick.Window 2.2 +import TrafficLightStateMachine 1.0 Window { id: root - property QtObject stateMachine + + property TrafficLightStateMachine stateMachine visible: true - color: "black" width: lights.width height: lights.height - Image { + Lights { id: lights - source: "background.png" - - MouseArea { - anchors.fill: parent - onClicked: { - Qt.quit(); - } - } - - Image { - id: redLight - x: (lights.width - width) / 2 - y: 40 - source: "red.png" - visible: stateMachine.red || stateMachine.redGoingGreen - } - - Image { - id: yellowLight - x: (lights.width - width) / 2 - y: 135 - source: "yellow.png" - visible: stateMachine.yellow || stateMachine.blinking - } - - Image { - id: greenLight - x: (lights.width - width) / 2 - y: 230 - source: "green.png" - visible: stateMachine.green - } - } - - Button { - id: button - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 20 - source: stateMachine.working ? "pause.png" : "play.png" + stateMachine: root.stateMachine + button.source: stateMachine.working ? "pause.png" : "play.png" - onClicked: { - if (stateMachine.working) - stateMachine.smash() - else - stateMachine.repair() - } + button.onClicked: stateMachine.submitEvent(stateMachine.working ? "smash" : "repair"); } } diff --git a/examples/scxml/trafficlight-common/doc/src/trafficlight-compiling.qdocinc b/examples/scxml/trafficlight-common/doc/src/trafficlight-compiling.qdocinc index fb00130..ac16203 100644 --- a/examples/scxml/trafficlight-common/doc/src/trafficlight-compiling.qdocinc +++ b/examples/scxml/trafficlight-common/doc/src/trafficlight-compiling.qdocinc @@ -12,8 +12,6 @@ \skipto STATECHARTS \printline scxml - We also tell qmake to run \c qscxmlc, which generates \e statemachine.h and - \e statemachine.cpp, and adds them to the \c HEADERS and \c SOURCES - variables for compilation: - - \printuntil qscxmlc + The Qt SCXML Compiler, \c qscxmlc, is run automatically to generate + \e statemachine.h and \e statemachine.cpp, and to add them to the \c HEADERS + and \c SOURCES variables for compilation. diff --git a/examples/scxml/trafficlight-common/statemachine.scxml b/examples/scxml/trafficlight-common/statemachine.scxml index 4224e81..3cf023e 100644 --- a/examples/scxml/trafficlight-common/statemachine.scxml +++ b/examples/scxml/trafficlight-common/statemachine.scxml @@ -50,7 +50,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" xmlns:qt="http://theqtcompany.com/scxml/2015/06/" diff --git a/examples/scxml/trafficlight-common/trafficlight.cpp b/examples/scxml/trafficlight-common/trafficlight.cpp index c14d4dc..66ed289 100644 --- a/examples/scxml/trafficlight-common/trafficlight.cpp +++ b/examples/scxml/trafficlight-common/trafficlight.cpp @@ -103,15 +103,15 @@ TrafficLight::TrafficLight(QScxmlStateMachine *machine, QWidget *parent) setFixedSize(widget->sizeHint()); machine->connectToState(QStringLiteral("red"), - widget->redLight(), SLOT(switchLight(bool))); + widget->redLight(), &LightWidget::switchLight); machine->connectToState(QStringLiteral("redGoingGreen"), - widget->redLight(), SLOT(switchLight(bool))); + widget->redLight(), &LightWidget::switchLight); machine->connectToState(QStringLiteral("yellow"), - widget->yellowLight(), SLOT(switchLight(bool))); + widget->yellowLight(), &LightWidget::switchLight); machine->connectToState(QStringLiteral("blinking"), - widget->yellowLight(), SLOT(switchLight(bool))); + widget->yellowLight(), &LightWidget::switchLight); machine->connectToState(QStringLiteral("green"), - widget->greenLight(), SLOT(switchLight(bool))); + widget->greenLight(), &LightWidget::switchLight); QAbstractButton *button = new ButtonWidget(this); auto setButtonGeometry = [this, button](){ diff --git a/examples/scxml/trafficlight-common/trafficlight.h b/examples/scxml/trafficlight-common/trafficlight.h index cf9e22f..f64feda 100644 --- a/examples/scxml/trafficlight-common/trafficlight.h +++ b/examples/scxml/trafficlight-common/trafficlight.h @@ -63,7 +63,7 @@ class TrafficLight : public QWidget public: TrafficLight(QScxmlStateMachine *machine, QWidget *parent = 0); -public slots: +private slots: void toggleWorking(bool pause); private: diff --git a/examples/scxml/trafficlight-qml-dynamic/doc/src/trafficlight-qml-dynamic.qdoc b/examples/scxml/trafficlight-qml-dynamic/doc/src/trafficlight-qml-dynamic.qdoc index ecbf090..0316508 100644 --- a/examples/scxml/trafficlight-qml-dynamic/doc/src/trafficlight-qml-dynamic.qdoc +++ b/examples/scxml/trafficlight-qml-dynamic/doc/src/trafficlight-qml-dynamic.qdoc @@ -61,8 +61,7 @@ We connect to the states as follows: - \quotefromfile trafficlight-common/TrafficLight.qml - \skipto /^ {8}Image/ - \printuntil greenLight - \printuntil /^ {8}\}/ + \quotefromfile trafficlight-common/Lights.ui.qml + \skipto states + \printuntil ] */ diff --git a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.cpp b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.cpp index b862386..a8bf00e 100644 --- a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.cpp +++ b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.cpp @@ -51,11 +51,20 @@ #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> +#include <QScxmlStateMachine> int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); + /* Register QScxmlStateMachine as TrafficLightStateMachine. This is required to have a type + * for the state machine and allows full code completion in the static case, since we + * share the QML code. */ + qmlRegisterUncreatableType<QScxmlStateMachine>("TrafficLightStateMachine", + 1, 0, + "TrafficLightStateMachine", + QLatin1String("TrafficLightStateMachine is not creatable.")); + QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:///trafficlight-qml-dynamic.qml"))); if (engine.rootObjects().isEmpty()) diff --git a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.pro b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.pro index 886424e..c03a7ef 100644 --- a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.pro +++ b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.pro @@ -6,7 +6,5 @@ SOURCES += trafficlight-qml-dynamic.cpp RESOURCES += trafficlight-qml-dynamic.qrc -load(qscxmlc) - target.path = $$[QT_INSTALL_EXAMPLES]/scxml/trafficlight-qml-dynamic INSTALLS += target diff --git a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qml b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qml index 772d2fb..aef249a 100644 --- a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qml +++ b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qml @@ -48,12 +48,12 @@ ** ****************************************************************************/ -import QtScxml 5.7 as Scxml +import QtScxml 5.8 TrafficLight { - Scxml.StateMachineLoader { + StateMachineLoader { id: loader - filename: "qrc:///statemachine.scxml" + source: "qrc:///statemachine.scxml" } stateMachine: loader.stateMachine diff --git a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qrc b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qrc index 475a7be..547935b 100644 --- a/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qrc +++ b/examples/scxml/trafficlight-qml-dynamic/trafficlight-qml-dynamic.qrc @@ -2,6 +2,7 @@ <qresource prefix="/"> <file alias="TrafficLight.qml">../trafficlight-common/TrafficLight.qml</file> <file alias="Button.qml">../trafficlight-common/Button.qml</file> + <file alias="Lights.ui.qml">../trafficlight-common/Lights.ui.qml</file> <file>trafficlight-qml-dynamic.qml</file> <file alias="statemachine.scxml">../trafficlight-common/statemachine.scxml</file> <file alias="green.png">../trafficlight-common/green.png</file> diff --git a/tests/auto/qscxmlc/data/noContentInInvoke3.scxml b/examples/scxml/trafficlight-qml-simple/Light.qml Binary files differindex 039f22b..8502d35 100644 --- a/tests/auto/qscxmlc/data/noContentInInvoke3.scxml +++ b/examples/scxml/trafficlight-qml-simple/Light.qml diff --git a/tests/auto/qscxmlc/data/badInitial.scxml b/examples/scxml/trafficlight-qml-simple/TrafficLight.qml index 52dffce..da51ded 100644 --- a/tests/auto/qscxmlc/data/badInitial.scxml +++ b/examples/scxml/trafficlight-qml-simple/TrafficLight.qml @@ -1,5 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -49,45 +47,62 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="MediaPlayerStateMachine" - initial="stopped" - datamodel="ecmascript" -> - <datamodel> - <data id="media"/> - </datamodel> - <script> - function isValidMedia() { - var m = _event.data.media - return (m + "").length > 0 +import QtQuick 2.5 +import QtQuick.Window 2.2 +import TrafficLightStateMachine 1.0 + +Window { + id: root + visible: true + width: 100 + height: 350 + + TrafficLightStateMachine { + id: stateMachine + running: true + } + + Item { + id: lights + width: parent.width + height: 300 + + Light { + anchors.top: parent.top + color: "red" + visible: stateMachine.red || stateMachine.redGoingGreen } - <initial> - <state id="stopped"> - <transition event="tap" cond="isValidMedia()" target="playing"/> - </state> + Light { + anchors.centerIn: parent + color: "yellow" + visible: stateMachine.yellow || stateMachine.blinking + } - <state id="playing"> - <onentry> - <assign location="media" expr="_event.data.media"/> - <send type="qt:signal" event="playbackStarted"> - <param name="media" expr="media"/> - </send> - </onentry> + Light { + anchors.bottom: parent.bottom + color: "green" + visible: stateMachine.green + } + } + + Rectangle { + anchors.top: lights.bottom + anchors.bottom: parent.bottom + width: parent.width + border.color: "black" - <onexit> - <send type="qt:signal" event="playbackStopped"> - <param name="media" expr="media"/> - </send> - </onexit> + Text { + anchors.fill: parent + text: stateMachine.working ? "Pause" : "Unpause" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } - <transition event="tap" cond="!isValidMedia() || media === _event.data.media" target="stopped"/> - <transition event="tap" cond="isValidMedia() && media !== _event.data.media" target="playing"/> - </state> -</scxml> + MouseArea { + anchors.fill: parent + onClicked: stateMachine.submitEvent(stateMachine.working ? "smash" : "repair"); + } + } +} diff --git a/examples/scxml/trafficlight-qml-simple/doc/images/trafficlight.png b/examples/scxml/trafficlight-qml-simple/doc/images/trafficlight.png Binary files differnew file mode 100644 index 0000000..ddd7431 --- /dev/null +++ b/examples/scxml/trafficlight-qml-simple/doc/images/trafficlight.png diff --git a/examples/scxml/trafficlight-qml-simple/doc/src/trafficlight-qml-simple.qdoc b/examples/scxml/trafficlight-qml-simple/doc/src/trafficlight-qml-simple.qdoc new file mode 100644 index 0000000..f66f5f7 --- /dev/null +++ b/examples/scxml/trafficlight-qml-simple/doc/src/trafficlight-qml-simple.qdoc @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example trafficlight-qml-simple + \title Qt SCXML Traffic Light QML Example (Simple) + \ingroup examples-qtscxml + + \brief A Qt Quick application that uses a compiled state machine to + implement a simplified traffic light. + + \image trafficlight.png + + \e{Traffic Light QML Example (Simple)} demonstrates how to connect to the + active properties of a state in a state machine that is compiled to a class. + + The UI is created using Qt Quick. + + \include examples-run.qdocinc + + \include trafficlight-compiling.qdocinc + + \section1 Instantiating the State Machine + + We instantiate the state machine as follows: + + \quotefromfile trafficlight-qml-simple/TrafficLight.qml + + \skipto TrafficLightStateMachine { + \printuntil } + + \include trafficlight-state-machine.qdocinc + + We connect to the states as follows: + + \quotefromfile trafficlight-qml-simple/TrafficLight.qml + \skipto Light { + \printuntil /^ {4}\}/ +*/ diff --git a/tests/auto/qscxmlc/data/noContentInInvoke2.scxml b/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.cpp index 8d7f017..c9950c4 100644 --- a/tests/auto/qscxmlc/data/noContentInInvoke2.scxml +++ b/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.cpp @@ -1,5 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -49,31 +47,25 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyp\ace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <contenon use t <sl name="anywhere version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - * Redistr </content> - </invoke> - , </state> - </state> -</scxml> +#include <QGuiApplication> +#include <QQmlApplicationEngine> +#include <QQmlContext> + +#include "statemachine.h" + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType<TrafficLightStateMachine>("TrafficLightStateMachine", 1, 0, + "TrafficLightStateMachine"); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/TrafficLight.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +} + diff --git a/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.pro b/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.pro new file mode 100644 index 0000000..4de04ea --- /dev/null +++ b/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.pro @@ -0,0 +1,14 @@ +TEMPLATE = app + +QT += qml scxml +CONFIG += c++11 + +SOURCES += trafficlight-qml-simple.cpp + +RESOURCES += trafficlight-qml-simple.qrc + +STATECHARTS = ../trafficlight-common/statemachine.scxml + +target.path = $$[QT_INSTALL_EXAMPLES]/scxml/trafficlight-qml-simple +INSTALLS += target + diff --git a/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.qrc b/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.qrc new file mode 100644 index 0000000..bb75dba --- /dev/null +++ b/examples/scxml/trafficlight-qml-simple/trafficlight-qml-simple.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/"> + <file>TrafficLight.qml</file> + <file>Light.qml</file> + </qresource> +</RCC> diff --git a/examples/scxml/trafficlight-qml-static/doc/src/trafficlight-qml-static.qdoc b/examples/scxml/trafficlight-qml-static/doc/src/trafficlight-qml-static.qdoc index a83de5f..814f410 100644 --- a/examples/scxml/trafficlight-qml-static/doc/src/trafficlight-qml-static.qdoc +++ b/examples/scxml/trafficlight-qml-static/doc/src/trafficlight-qml-static.qdoc @@ -57,8 +57,7 @@ We connect to the states as follows: - \quotefromfile trafficlight-common/TrafficLight.qml - \skipto /^ {8}Image/ - \printuntil greenLight - \printuntil /^ {8}\}/ + \quotefromfile trafficlight-common/Lights.ui.qml + \skipto states + \printuntil ] */ diff --git a/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.pro b/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.pro index 4c19594..e5dd64c 100644 --- a/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.pro +++ b/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.pro @@ -9,8 +9,6 @@ RESOURCES += trafficlight-qml-static.qrc STATECHARTS = ../trafficlight-common/statemachine.scxml -load(qscxmlc) - target.path = $$[QT_INSTALL_EXAMPLES]/scxml/trafficlight-qml-static INSTALLS += target diff --git a/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.qrc b/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.qrc index 46c408f..890b4a7 100644 --- a/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.qrc +++ b/examples/scxml/trafficlight-qml-static/trafficlight-qml-static.qrc @@ -3,6 +3,7 @@ <file>trafficlight-qml-static.qml</file> <file alias="TrafficLight.qml">../trafficlight-common/TrafficLight.qml</file> <file alias="Button.qml">../trafficlight-common/Button.qml</file> + <file alias="Lights.ui.qml">../trafficlight-common/Lights.ui.qml</file> <file alias="green.png">../trafficlight-common/green.png</file> <file alias="yellow.png">../trafficlight-common/yellow.png</file> <file alias="red.png">../trafficlight-common/red.png</file> diff --git a/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.cpp b/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.cpp index cc0044d..7b0489e 100644 --- a/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.cpp +++ b/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.cpp @@ -51,7 +51,6 @@ #include "../trafficlight-common/trafficlight.h" #include <QApplication> -#include <QScxmlNullDataModel> #include <QTextStream> int main(int argc, char **argv) @@ -61,7 +60,8 @@ int main(int argc, char **argv) QScxmlStateMachine *machine = QScxmlStateMachine::fromFile(QStringLiteral(":statemachine.scxml")); if (!machine->parseErrors().isEmpty()) { QTextStream errs(stderr, QIODevice::WriteOnly); - foreach (const QScxmlError &error, machine->parseErrors()) { + const auto errors = machine->parseErrors(); + for (const QScxmlError &error : errors) { errs << error.toString(); } diff --git a/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.pro b/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.pro index 21da884..5613249 100644 --- a/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.pro +++ b/examples/scxml/trafficlight-widgets-dynamic/trafficlight-widgets-dynamic.pro @@ -9,7 +9,5 @@ SOURCES += trafficlight-widgets-dynamic.cpp target.path = $$[QT_INSTALL_EXAMPLES]/scxml/trafficlight-widgets-dynamic INSTALLS += target -load(qscxmlc) - RESOURCES += \ trafficlight-widgets-dynamic.qrc diff --git a/examples/scxml/trafficlight-widgets-static/trafficlight-widgets-static.pro b/examples/scxml/trafficlight-widgets-static/trafficlight-widgets-static.pro index c98fa98..8a32984 100644 --- a/examples/scxml/trafficlight-widgets-static/trafficlight-widgets-static.pro +++ b/examples/scxml/trafficlight-widgets-static/trafficlight-widgets-static.pro @@ -5,8 +5,6 @@ SOURCES = ../trafficlight-common/trafficlight.cpp HEADERS = ../trafficlight-common/trafficlight.h STATECHARTS = ../trafficlight-common/statemachine.scxml -load(qscxmlc) - SOURCES += trafficlight-widgets-static.cpp # install diff --git a/mkspecs/features/qscxmlc.prf b/mkspecs/features/qscxmlc.prf index 4631377..25f1f10 100644 --- a/mkspecs/features/qscxmlc.prf +++ b/mkspecs/features/qscxmlc.prf @@ -9,13 +9,12 @@ debug_and_release { QSCXMLC_DIR = $$QSCXMLC_DIR$$SUFFIX -msvc:lessThan(MSC_VER,1800):QMAKE_QSCXMLC=$$QMAKE_QSCXMLC --no-c++11 # VS2012 cannot handle initializer lists. - { qscxmlc.name = QSCXMLC ${QMAKE_FILE_IN}.h qscxmlc.input = STATECHARTS qscxmlc.variable_out = QSCXMLC_HEADERS qscxmlc.commands = $$QMAKE_QSCXMLC ${QMAKE_FILE_IN} --header ${QMAKE_FILE_OUT} --impl $$QSCXMLC_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} +!isEmpty(QSCXMLC_NAMESPACE): qscxmlc.commands = $${qscxmlc.commands} --namespace $$QSCXMLC_NAMESPACE qscxmlc.output = $$QSCXMLC_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_H)} qscxmlc.CONFIG += target_predeps qscxmlc.depends += $$QMAKE_QSCXMLC_EXE diff --git a/src/imports/scxmlstatemachine/eventconnection.cpp b/src/imports/scxmlstatemachine/eventconnection.cpp new file mode 100644 index 0000000..9803a9a --- /dev/null +++ b/src/imports/scxmlstatemachine/eventconnection.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "eventconnection_p.h" + +QT_BEGIN_NAMESPACE + +QScxmlEventConnection::QScxmlEventConnection(QObject *parent) : + QObject(parent), m_stateMachine(nullptr) +{ +} + +QStringList QScxmlEventConnection::events() const +{ + return m_events; +} + +void QScxmlEventConnection::setEvents(const QStringList &events) +{ + if (events != m_events) { + m_events = events; + doConnect(); + emit eventsChanged(); + } +} + +QScxmlStateMachine *QScxmlEventConnection::stateMachine() const +{ + return m_stateMachine; +} + +void QScxmlEventConnection::setStateMachine(QScxmlStateMachine *stateMachine) +{ + if (stateMachine != m_stateMachine) { + m_stateMachine = stateMachine; + doConnect(); + emit stateMachineChanged(); + } +} + +void QScxmlEventConnection::doConnect() +{ + for (const QMetaObject::Connection &connection : qAsConst(m_connections)) + disconnect(connection); + m_connections.clear(); + if (m_stateMachine) { + for (const QString &event : qAsConst(m_events)) { + m_connections.append(m_stateMachine->connectToEvent(event, this, + &QScxmlEventConnection::occurred)); + } + + } + +} + +void QScxmlEventConnection::classBegin() +{ +} + +void QScxmlEventConnection::componentComplete() +{ + if (!m_stateMachine) { + if ((m_stateMachine = qobject_cast<QScxmlStateMachine *>(parent()))) + doConnect(); + } +} + +QT_END_NAMESPACE diff --git a/src/imports/scxmlstatemachine/eventconnection_p.h b/src/imports/scxmlstatemachine/eventconnection_p.h new file mode 100644 index 0000000..5fea645 --- /dev/null +++ b/src/imports/scxmlstatemachine/eventconnection_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef EVENTCONNECTION_P_H +#define EVENTCONNECTION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtScxml/qscxmlstatemachine.h> +#include <QtCore/qobject.h> +#include <QtQml/qqmlparserstatus.h> + +QT_BEGIN_NAMESPACE + +class QScxmlEventConnection : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QStringList events READ events WRITE setEvents NOTIFY eventsChanged) + Q_PROPERTY(QScxmlStateMachine *stateMachine READ stateMachine WRITE setStateMachine + NOTIFY stateMachineChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + QScxmlEventConnection(QObject *parent = nullptr); + + QStringList events() const; + void setEvents(const QStringList &events); + + QScxmlStateMachine *stateMachine() const; + void setStateMachine(QScxmlStateMachine *stateMachine); + +Q_SIGNALS: + void eventsChanged(); + void stateMachineChanged(); + + void occurred(const QScxmlEvent &event); + +private: + QScxmlStateMachine *m_stateMachine; + QStringList m_events; + + QList<QMetaObject::Connection> m_connections; + + void doConnect(); + void classBegin() override; + void componentComplete() override; +}; + +QT_END_NAMESPACE + +#endif // EVENTCONNECTION_P_H diff --git a/src/imports/scxmlstatemachine/invokedservices.cpp b/src/imports/scxmlstatemachine/invokedservices.cpp new file mode 100644 index 0000000..93c97d6 --- /dev/null +++ b/src/imports/scxmlstatemachine/invokedservices.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "invokedservices_p.h" +#include <QtScxml/qscxmlinvokableservice.h> + +QT_BEGIN_NAMESPACE + +QScxmlInvokedServices::QScxmlInvokedServices(QObject *parent) : QObject(parent) +{ +} + +QVariantMap QScxmlInvokedServices::children() +{ + QVariantMap ret; + if (m_stateMachine) { + const QVector<QScxmlInvokableService *> children = m_stateMachine->invokedServices(); + for (QScxmlInvokableService *service : children) + ret.insertMulti(service->name(), QVariant::fromValue(service)); + } + return ret; +} + +void QScxmlInvokedServices::classBegin() +{ +} + +QScxmlStateMachine *QScxmlInvokedServices::stateMachine() const +{ + return m_stateMachine; +} + +void QScxmlInvokedServices::setStateMachine(QScxmlStateMachine *stateMachine) +{ + if (stateMachine != m_stateMachine) { + if (m_stateMachine) { + disconnect(m_stateMachine, &QScxmlStateMachine::invokedServicesChanged, + this, &QScxmlInvokedServices::childrenChanged); + } + m_stateMachine = stateMachine; + connect(m_stateMachine, &QScxmlStateMachine::invokedServicesChanged, + this, &QScxmlInvokedServices::childrenChanged); + emit stateMachineChanged(); + emit childrenChanged(); + } +} + +QQmlListProperty<QObject> QScxmlInvokedServices::qmlChildren() +{ + return QQmlListProperty<QObject>(this, m_qmlChildren); +} + + +void QScxmlInvokedServices::componentComplete() +{ + if (!m_stateMachine) { + if ((m_stateMachine = qobject_cast<QScxmlStateMachine *>(parent()))) { + connect(m_stateMachine, &QScxmlStateMachine::invokedServicesChanged, + this, &QScxmlInvokedServices::childrenChanged); + } + } +} + +QT_END_NAMESPACE diff --git a/src/imports/scxmlstatemachine/invokedservices_p.h b/src/imports/scxmlstatemachine/invokedservices_p.h new file mode 100644 index 0000000..72d6f2a --- /dev/null +++ b/src/imports/scxmlstatemachine/invokedservices_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef INVOKEDSERVICES_P_H +#define INVOKEDSERVICES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/qqmlparserstatus.h> +#include <QtQml/qqmllist.h> +#include <QtScxml/qscxmlstatemachine.h> + +QT_BEGIN_NAMESPACE + +class QScxmlInvokedServices : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QScxmlStateMachine *stateMachine READ stateMachine WRITE setStateMachine + NOTIFY stateMachineChanged) + Q_PROPERTY(QVariantMap children READ children NOTIFY childrenChanged) + Q_PROPERTY(QQmlListProperty<QObject> qmlChildren READ qmlChildren) + Q_INTERFACES(QQmlParserStatus) + Q_CLASSINFO("DefaultProperty", "qmlChildren") +public: + QScxmlInvokedServices(QObject *parent = 0); + QVariantMap children(); + + QScxmlStateMachine *stateMachine() const; + void setStateMachine(QScxmlStateMachine *stateMachine); + + QQmlListProperty<QObject> qmlChildren(); + +Q_SIGNALS: + void childrenChanged(); + void stateMachineChanged(); + +private: + void classBegin() override; + void componentComplete() override; + + QScxmlStateMachine *m_stateMachine = 0; + QList<QObject *> m_qmlChildren; +}; + +QT_END_NAMESPACE + +#endif // INVOKEDSERVICES_P_H diff --git a/src/imports/scxmlstatemachine/plugin.cpp b/src/imports/scxmlstatemachine/plugin.cpp index 1837935..6842846 100644 --- a/src/imports/scxmlstatemachine/plugin.cpp +++ b/src/imports/scxmlstatemachine/plugin.cpp @@ -37,8 +37,11 @@ ** ****************************************************************************/ -#include "statemachineloader.h" +#include "statemachineloader_p.h" +#include "eventconnection_p.h" #include "qscxmlevent.h" +#include "statemachineextended_p.h" +#include "invokedservices_p.h" #include <QQmlExtensionPlugin> #include <qqml.h> @@ -57,7 +60,7 @@ public: Q_ASSERT(uri == QStringLiteral("QtScxml")); int major = 5; - int minor = 7; + int minor = 8; // Do not rely on RegisterMethodArgumentMetaType meta-call to register the QScxmlEvent type. // This registration is required for the receiving end of the signal emission that carries // parameters of this type to be able to treat them correctly as a gadget. This is because the @@ -66,6 +69,10 @@ public: static const int qScxmlEventMetaTypeId = qMetaTypeId<QScxmlEvent>(); Q_UNUSED(qScxmlEventMetaTypeId) qmlRegisterType<QScxmlStateMachineLoader>(uri, major, minor, "StateMachineLoader"); + qmlRegisterType<QScxmlEventConnection>(uri, major, minor, "EventConnection"); + qmlRegisterType<QScxmlInvokedServices>(uri, major, minor, "InvokedServices"); + qmlRegisterExtendedUncreatableType<QScxmlStateMachine, QScxmlStateMachineExtended>( + uri, major, minor, "StateMachine", "Only created through derived types"); qmlProtectModule(uri, 1); } }; diff --git a/src/imports/scxmlstatemachine/plugins.qmltypes b/src/imports/scxmlstatemachine/plugins.qmltypes index 1366717..a7a72e7 100644 --- a/src/imports/scxmlstatemachine/plugins.qmltypes +++ b/src/imports/scxmlstatemachine/plugins.qmltypes @@ -4,16 +4,134 @@ import QtQuick.tooling 1.2 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -nonrelocatable QtScxml 5.7' +// 'qmlplugindump -nonrelocatable QtScxml 5.8' Module { - dependencies: ["QtQuick 2.0"] + dependencies: ["QtQuick 2.8"] + Component { + name: "QScxmlEventConnection" + prototype: "QObject" + exports: ["QtScxml/EventConnection 5.8"] + exportMetaObjectRevisions: [0] + Property { name: "events"; type: "QStringList" } + Property { name: "stateMachine"; type: "QScxmlStateMachine"; isPointer: true } + Signal { + name: "occurred" + Parameter { name: "event"; type: "QScxmlEvent" } + } + } + Component { + name: "QScxmlInvokedServices" + defaultProperty: "qmlChildren" + prototype: "QObject" + exports: ["QtScxml/InvokedServices 5.8"] + exportMetaObjectRevisions: [0] + Property { name: "stateMachine"; type: "QScxmlStateMachine"; isPointer: true } + Property { name: "children"; type: "QVariantMap"; isReadonly: true } + Property { name: "qmlChildren"; type: "QObject"; isList: true; isReadonly: true } + } + Component { + name: "QScxmlStateMachine" + prototype: "QObject" + Property { name: "running"; type: "bool" } + Property { name: "initialized"; type: "bool"; isReadonly: true } + Property { name: "dataModel"; type: "QScxmlDataModel"; isPointer: true } + Property { name: "initialValues"; type: "QVariantMap" } + Property { name: "invokedServices"; type: "QVector<QScxmlInvokableService*>"; isReadonly: true } + Property { name: "sessionId"; type: "string"; isReadonly: true } + Property { name: "name"; type: "string"; isReadonly: true } + Property { name: "invoked"; type: "bool"; isReadonly: true } + Property { name: "parseErrors"; type: "QVector<QScxmlError>"; isReadonly: true } + Property { name: "loader"; type: "QScxmlParser::Loader"; isPointer: true } + Signal { + name: "runningChanged" + Parameter { name: "running"; type: "bool" } + } + Signal { + name: "invokedServicesChanged" + Parameter { name: "invokedServices"; type: "QVector<QScxmlInvokableService*>" } + } + Signal { + name: "log" + Parameter { name: "label"; type: "string" } + Parameter { name: "msg"; type: "string" } + } + Signal { name: "reachedStableState" } + Signal { name: "finished" } + Signal { + name: "dataModelChanged" + Parameter { name: "model"; type: "QScxmlDataModel"; isPointer: true } + } + Signal { + name: "initialValuesChanged" + Parameter { name: "initialValues"; type: "QVariantMap" } + } + Signal { + name: "initializedChanged" + Parameter { name: "initialized"; type: "bool" } + } + Signal { + name: "loaderChanged" + Parameter { name: "loader"; type: "QScxmlParser::Loader"; isPointer: true } + } + Method { name: "start" } + Method { name: "stop" } + Method { name: "init"; type: "bool" } + Method { + name: "stateNames" + type: "QStringList" + Parameter { name: "compress"; type: "bool" } + } + Method { name: "stateNames"; type: "QStringList" } + Method { + name: "activeStateNames" + type: "QStringList" + Parameter { name: "compress"; type: "bool" } + } + Method { name: "activeStateNames"; type: "QStringList" } + Method { + name: "isActive" + type: "bool" + Parameter { name: "scxmlStateName"; type: "string" } + } + Method { + name: "submitEvent" + Parameter { name: "event"; type: "QScxmlEvent"; isPointer: true } + } + Method { + name: "submitEvent" + Parameter { name: "eventName"; type: "string" } + } + Method { + name: "submitEvent" + Parameter { name: "eventName"; type: "string" } + Parameter { name: "data"; type: "QVariant" } + } + Method { + name: "cancelDelayedEvent" + Parameter { name: "sendId"; type: "string" } + } + Method { + name: "isDispatchableTarget" + type: "bool" + Parameter { name: "target"; type: "string" } + } + } + Component { + name: "QScxmlStateMachineExtended" + defaultProperty: "children" + prototype: "QScxmlStateMachine" + exports: ["QtScxml/StateMachine 5.8"] + isCreatable: false + exportMetaObjectRevisions: [0] + Property { name: "children"; type: "QObject"; isList: true; isReadonly: true } + } Component { name: "QScxmlStateMachineLoader" prototype: "QObject" - exports: ["QtScxml/StateMachineLoader 5.7"] + exports: ["QtScxml/StateMachineLoader 5.8"] exportMetaObjectRevisions: [0] - Property { name: "filename"; type: "QUrl" } + Property { name: "source"; type: "QUrl" } Property { name: "stateMachine"; type: "QScxmlStateMachine"; isReadonly: true; isPointer: true } Property { name: "initialValues"; type: "QVariantMap" } Property { name: "dataModel"; type: "QScxmlDataModel"; isPointer: true } diff --git a/src/imports/scxmlstatemachine/scxmlstatemachine.pro b/src/imports/scxmlstatemachine/scxmlstatemachine.pro index 23497ac..a45a5f3 100644 --- a/src/imports/scxmlstatemachine/scxmlstatemachine.pro +++ b/src/imports/scxmlstatemachine/scxmlstatemachine.pro @@ -5,10 +5,17 @@ QT = scxml qml-private core-private SOURCES = \ $$PWD/plugin.cpp \ - $$PWD/statemachineloader.cpp + $$PWD/statemachineloader.cpp \ + $$PWD/eventconnection.cpp \ + $$PWD/statemachineextended.cpp \ + $$PWD/invokedservices.cpp HEADERS = \ - $$PWD/statemachineloader.h + $$PWD/eventconnection_p.h \ + $$PWD/invokedservices_p.h \ + $$PWD/statemachineextended_p.h \ + $$PWD/statemachineloader_p.h + load(qml_plugin) diff --git a/src/imports/scxmlstatemachine/statemachineextended.cpp b/src/imports/scxmlstatemachine/statemachineextended.cpp new file mode 100644 index 0000000..6b1b13b --- /dev/null +++ b/src/imports/scxmlstatemachine/statemachineextended.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "statemachineextended_p.h" + +#include <QtScxml/qscxmlglobals.h> +#include <QtScxml/qscxmlstatemachine.h> + +QT_BEGIN_NAMESPACE + +QScxmlStateMachineExtended::QScxmlStateMachineExtended(QObject *extendee) : + QObject(extendee) +{ +} + +QQmlListProperty<QObject> QScxmlStateMachineExtended::children() +{ + return QQmlListProperty<QObject>(this, m_children); +} + +QT_END_NAMESPACE diff --git a/src/imports/scxmlstatemachine/statemachineextended_p.h b/src/imports/scxmlstatemachine/statemachineextended_p.h new file mode 100644 index 0000000..ab069b1 --- /dev/null +++ b/src/imports/scxmlstatemachine/statemachineextended_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef STATEMACHINEEXTENDED_P_H +#define STATEMACHINEEXTENDED_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtScxml/qscxmlglobals.h> +#include <QtCore/qobject.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +/* Allow State Machines created from QML to have children. */ +class QScxmlStateMachineExtended : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQmlListProperty<QObject> children READ children) + Q_CLASSINFO("DefaultProperty", "children") +public: + QScxmlStateMachineExtended(QObject *extendee); + QQmlListProperty<QObject> children(); + +private: + QObjectList m_children; +}; + +QT_END_NAMESPACE + +#endif // STATEMACHINEEXTENDED_P_H diff --git a/src/imports/scxmlstatemachine/statemachineloader.cpp b/src/imports/scxmlstatemachine/statemachineloader.cpp index 68bdae8..cc908c6 100644 --- a/src/imports/scxmlstatemachine/statemachineloader.cpp +++ b/src/imports/scxmlstatemachine/statemachineloader.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ -#include "statemachineloader.h" +#include "statemachineloader_p.h" #include <QtScxml/qscxmlstatemachine.h> #include <QQmlContext> @@ -49,20 +49,23 @@ /*! \qmltype StateMachineLoader \instantiates QScxmlStateMachineLoader - \inqmlmodule Scxml + \inqmlmodule QtScxml - \brief Dynamically loads an SCXML file and instantiates the state machine. + \brief Dynamically loads an SCXML document and instantiates the state machine. \since QtScxml 5.7 */ /*! - \qmlsignal StateMachineLoader::filenameChanged() - This signal is emitted when the user changes the filename. + \qmlsignal StateMachineLoader::sourceChanged() + This signal is emitted when the user changes the source URL for the SCXML document. */ /*! \qmlsignal StateMachineLoader::stateMachineChanged() + + This signal is emitted when the stateMachine property changes. That is, when + a new state machine is loaded or when the old one becomes invalid. */ QScxmlStateMachineLoader::QScxmlStateMachineLoader(QObject *parent) @@ -84,34 +87,34 @@ QT_PREPEND_NAMESPACE(QScxmlStateMachine) *QScxmlStateMachineLoader::stateMachine } /*! - \qmlproperty string StateMachineLoader::filename + \qmlproperty string StateMachineLoader::source - The name of the SCXML file to load. + The url of the SCXML document to load. Only synchronously accessible URLs are supported. */ -QUrl QScxmlStateMachineLoader::filename() +QUrl QScxmlStateMachineLoader::source() { - return m_filename; + return m_source; } -void QScxmlStateMachineLoader::setFilename(const QUrl &filename) +void QScxmlStateMachineLoader::setSource(const QUrl &source) { - if (!filename.isValid()) + if (!source.isValid()) return; - QUrl oldFilename = m_filename; + QUrl oldSource = m_source; if (m_stateMachine) { delete m_stateMachine; m_stateMachine = Q_NULLPTR; m_implicitDataModel = Q_NULLPTR; } - if (parse(filename)) { - m_filename = filename; - emit filenameChanged(); + if (parse(source)) { + m_source = source; + emit sourceChanged(); } else { - m_filename.clear(); - if (!oldFilename.isEmpty()) { - emit filenameChanged(); + m_source.clear(); + if (!oldSource.isEmpty()) { + emit sourceChanged(); } } } @@ -150,16 +153,17 @@ void QScxmlStateMachineLoader::setDataModel(QScxmlDataModel *dataModel) } } -bool QScxmlStateMachineLoader::parse(const QUrl &filename) +bool QScxmlStateMachineLoader::parse(const QUrl &source) { - if (!QQmlFile::isSynchronous(filename)) { - qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading: only synchronous file access is supported.").arg(filename.fileName()); + if (!QQmlFile::isSynchronous(source)) { + qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading: only synchronous access is supported.") + .arg(source.url()); return false; } - QQmlFile scxmlFile(QQmlEngine::contextForObject(this)->engine(), filename); + QQmlFile scxmlFile(QQmlEngine::contextForObject(this)->engine(), source); if (scxmlFile.isError()) { // the synchronous case can only fail when the file is not found (or not readable). - qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading.").arg(filename.fileName()); + qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading.").arg(source.url()); return false; } @@ -170,7 +174,7 @@ bool QScxmlStateMachineLoader::parse(const QUrl &filename) return false; } - m_stateMachine = QScxmlStateMachine::fromData(&buf, filename.toString()); + m_stateMachine = QScxmlStateMachine::fromData(&buf, source.toString()); m_stateMachine->setParent(this); m_implicitDataModel = m_stateMachine->dataModel(); @@ -185,9 +189,12 @@ bool QScxmlStateMachineLoader::parse(const QUrl &filename) QMetaObject::invokeMethod(m_stateMachine, "start", Qt::QueuedConnection); return true; } else { - qmlInfo(this) << QStringLiteral("Something went wrong while parsing '%1':").arg(filename.fileName()) << endl; - foreach (const QScxmlError &msg, m_stateMachine->parseErrors()) { - qmlInfo(this) << msg.toString(); + qmlInfo(this) << QStringLiteral("Something went wrong while parsing '%1':") + .arg(source.url()) + << endl; + const auto errors = m_stateMachine->parseErrors(); + for (const QScxmlError &error : errors) { + qmlInfo(this) << error.toString(); } emit stateMachineChanged(); diff --git a/src/imports/scxmlstatemachine/statemachineloader.h b/src/imports/scxmlstatemachine/statemachineloader_p.h index 0e73a50..6eaf6f4 100644 --- a/src/imports/scxmlstatemachine/statemachineloader.h +++ b/src/imports/scxmlstatemachine/statemachineloader_p.h @@ -37,8 +37,19 @@ ** ****************************************************************************/ -#ifndef STATEMACHINELOADER_H -#define STATEMACHINELOADER_H +#ifndef STATEMACHINELOADER_P_H +#define STATEMACHINELOADER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// #include <QUrl> #include <QtScxml/qscxmlstatemachine.h> @@ -49,7 +60,7 @@ QT_BEGIN_NAMESPACE class QScxmlStateMachineLoader: public QObject { Q_OBJECT - Q_PROPERTY(QUrl filename READ filename WRITE setFilename NOTIFY filenameChanged) + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(QScxmlStateMachine* stateMachine READ stateMachine DESIGNABLE false NOTIFY stateMachineChanged) Q_PROPERTY(QVariantMap initialValues READ initialValues WRITE setInitialValues NOTIFY initialValuesChanged) Q_PROPERTY(QScxmlDataModel* dataModel READ dataModel WRITE setDataModel NOTIFY dataModelChanged) @@ -60,8 +71,8 @@ public: QScxmlStateMachine *stateMachine() const; - QUrl filename(); - void setFilename(const QUrl &filename); + QUrl source(); + void setSource(const QUrl &source); QVariantMap initialValues() const; void setInitialValues(const QVariantMap &initialValues); @@ -70,16 +81,16 @@ public: void setDataModel(QScxmlDataModel *dataModel); Q_SIGNALS: - void filenameChanged(); + void sourceChanged(); void initialValuesChanged(); void stateMachineChanged(); void dataModelChanged(); private: - bool parse(const QUrl &filename); + bool parse(const QUrl &source); private: - QUrl m_filename; + QUrl m_source; QVariantMap m_initialValues; QScxmlDataModel *m_dataModel; QScxmlDataModel *m_implicitDataModel; @@ -88,4 +99,4 @@ private: QT_END_NAMESPACE -#endif // STATEMACHINELOADER_H +#endif // STATEMACHINELOADER_P_H diff --git a/src/scxml/Qt5ScxmlConfigExtras.cmake.in b/src/scxml/Qt5ScxmlConfigExtras.cmake.in new file mode 100644 index 0000000..edb320a --- /dev/null +++ b/src/scxml/Qt5ScxmlConfigExtras.cmake.in @@ -0,0 +1,51 @@ +# +# Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the QtScxml module of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:LGPL$ +# 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 Lesser General Public License Usage +# Alternatively, this file may be used under the terms of the GNU Lesser +# General Public License version 3 as published by the Free Software +# Foundation and appearing in the file LICENSE.LGPL3 included in the +# packaging of this file. Please review the following information to +# ensure the GNU Lesser General Public License version 3 requirements +# will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 2.0 or (at your option) the GNU General +# Public license version 3 or any later version approved by the KDE Free +# Qt Foundation. The licenses are as published by the Free Software +# Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +# 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-2.0.html and +# https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ + +if (NOT TARGET Qt5::qscxmlc) + add_executable(Qt5::qscxmlc IMPORTED) + +!!IF isEmpty(CMAKE_BIN_DIR_IS_ABSOLUTE) + set(imported_location \"${_qt5Scxml_install_prefix}/$${CMAKE_BIN_DIR}qscxmlc$$CMAKE_BIN_SUFFIX\") +!!ELSE + set(imported_location \"$${CMAKE_BIN_DIR}qscxmlc$$CMAKE_BIN_SUFFIX\") +!!ENDIF + _qt5_Scxml_check_file_exists(${imported_location}) + + set_target_properties(Qt5::qscxmlc PROPERTIES + IMPORTED_LOCATION ${imported_location} + ) + get_target_property(Qt5Scxml_QSCXMLC_EXECUTABLE Qt5::qscxmlc LOCATION) +endif() diff --git a/src/scxml/Qt5ScxmlMacros.cmake b/src/scxml/Qt5ScxmlMacros.cmake new file mode 100644 index 0000000..234258c --- /dev/null +++ b/src/scxml/Qt5ScxmlMacros.cmake @@ -0,0 +1,67 @@ +# +# Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the QtScxml module of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:LGPL$ +# 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 Lesser General Public License Usage +# Alternatively, this file may be used under the terms of the GNU Lesser +# General Public License version 3 as published by the Free Software +# Foundation and appearing in the file LICENSE.LGPL3 included in the +# packaging of this file. Please review the following information to +# ensure the GNU Lesser General Public License version 3 requirements +# will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 2.0 or (at your option) the GNU General +# Public license version 3 or any later version approved by the KDE Free +# Qt Foundation. The licenses are as published by the Free Software +# Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +# 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-2.0.html and +# https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ + +if(NOT Qt5Scxml_QSCXMLC_EXECUTABLE) + message(FATAL_ERROR "qscxmlc executable not found -- Check installation.") +endif() + +# qt5_add_statecharts(outfiles inputfile ... ) + +function(qt5_add_statecharts outfiles) + set(options) + set(oneValueArgs) + set(multiValueArgs OPTIONS) + + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(scxml_files ${ARGS_UNPARSED_ARGUMENTS}) + + foreach(it ${scxml_files}) + get_filename_component(outfilename ${it} NAME_WE) + get_filename_component(infile ${it} ABSOLUTE) + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${outfilename}) + set(outfile_cpp ${CMAKE_CURRENT_BINARY_DIR}/${outfilename}.cpp) + set(outfile_h ${CMAKE_CURRENT_BINARY_DIR}/${outfilename}.h) + + add_custom_command(OUTPUT ${outfile_cpp} ${outfile_h} + COMMAND ${Qt5Scxml_QSCXMLC_EXECUTABLE} + ARGS ${ARGS_OPTIONS} --output ${outfile} ${infile} + MAIN_DEPENDENCY ${infile} + VERBATIM) + list(APPEND ${outfiles} ${outfile_cpp}) + endforeach() + set(${outfiles} ${${outfiles}} PARENT_SCOPE) +endfunction() diff --git a/src/scxml/doc/external-resources.qdoc b/src/scxml/doc/external-resources.qdoc index 45c3d08..3cbdc6f 100644 --- a/src/scxml/doc/external-resources.qdoc +++ b/src/scxml/doc/external-resources.qdoc @@ -58,3 +58,8 @@ \externalpage http://www.w3.org/TR/scxml/#N11630 \title SCXML Specification - Calculator Example */ + +/*! + \externalpage https://tools.ietf.org/html/rfc959 + \title RFC 959 +*/ diff --git a/src/scxml/doc/qtscxml-examples.qdoc b/src/scxml/doc/qtscxml-examples.qdoc index a3130bc..cc0b6c4 100644 --- a/src/scxml/doc/qtscxml-examples.qdoc +++ b/src/scxml/doc/qtscxml-examples.qdoc @@ -41,8 +41,8 @@ example version folder. All versions of an example application have the same appearance and fuctionality. They demonstrate the different options for creating user -interfaces (using Qt Widgets or Qt Quick) and for loading the SCXML dynamically -versus first compiling it to a C++ class (the \e static versions). +interfaces (using \l {Qt Widgets} or \l {Qt Quick}) and for loading the SCXML +dynamically versus first compiling it to a C++ class (the \e static versions). In addition, the Media Player example versions demonstrate how to access the C++ and ECMAScript data models. diff --git a/src/scxml/doc/qtscxml-index.qdoc b/src/scxml/doc/qtscxml-index.qdoc index 0de9624..7fd77bb 100644 --- a/src/scxml/doc/qtscxml-index.qdoc +++ b/src/scxml/doc/qtscxml-index.qdoc @@ -37,9 +37,6 @@ the state machine. It also contains functionality to support data models and executable content. - \note In Qt 5.7 the Qt SCXML module is released as Technology Preview. - This means that the API may still change in subsequent releases. - \section1 Getting Started To include the definitions of the module's classes, use the following directive: @@ -52,7 +49,7 @@ in your .qml file: \code - import QtScxml 5.7 + import QtScxml 5.8 \endcode To link against the module, add this line to your qmake .pro file: diff --git a/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc b/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc index 3b8d6aa..c431653 100644 --- a/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc +++ b/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc @@ -43,7 +43,7 @@ Or, in QML: \qml - import QtScxml 5.7 + import QtScxml 5.8 Item { property QtObject stateMachine: scxmlLoader.stateMachine @@ -68,15 +68,22 @@ MyStatemachine stateMachine; \endcode - To use a compiled state machine in QML, you can assign it to a context - property: + To use a compiled state machine in QML, you can register it as a QML type: \code - MyStatemachine stateMachine; - QQmlApplicationEngine engine; - engine.rootContext()->setContextProperty("stateMachine", &stateMachine); + qmlRegisterType<MyStateMachine>("MyStateMachine", 1, 0, "MyStateMachine"); \endcode + Then you can instantiate it in QML, like this: + + \qml + import MyStateMachine 1.0 + + MyStateMachine { + id: stateMachine + } + \endqml + To compile a state machine, the following lines have to be added to a .pro file: @@ -87,7 +94,10 @@ This will tell qmake to run \e qscxmlc which generates MyStatemachine.h and MyStatemachine.cpp, and adds them to \l [QMake] HEADERS and - \l [QMake] SOURCES variables. + \l [QMake] SOURCES variables. By default, the generated files are saved in + the build directory. The \e QSCXMLC_DIR variable can be set to specify + another directory. The \e QSCXMLC_NAMESPACE variable can be set to put the + state machine code into a C++ namespace. After instantiating a state machine, you can connect to any state's active property as follows. For example, if the state machine for a @@ -95,9 +105,8 @@ convenient id "red" in the scxml file), you can write: \code - QObject::connect(stateMachine, &TrafficLightStateMachine::redChanged, - [](bool active) { - qDebug() << (active ? "entered" : "exited") << "the red state"; + stateMachine->connectToState("red", [](bool active) { + qDebug() << (active ? "entered" : "exited") << "the red state"; \endcode And in QML: @@ -116,7 +125,7 @@ event, you can write: \code - QObject::connect(stateMachine, &MediaPlayer::playbackStopped, [](){ + stateMachine->connectToEvent("playbackStopped", [](const QScxmlEvent &){ qDebug() << "Stopped!"; }); \endcode @@ -124,17 +133,19 @@ And in QML: \qml - Connections { - target: stateMachine - onPlaybackStopped: console.log("Stopped!") + import QtScxml 5.8 + + EventConnection { + stateMachine: stateMachine + events: ["playbackStopped"] + onOccurred: console.log("Stopped!") } \endqml - Sending events to a state machine is equally simple. You can call (or - connect to) the slot: + Sending events to a state machine is equally simple: \code - stateMachine->tap(QVariantMap({ + stateMachine->submitEvent("tap", QVariantMap({ std::make_pair("artist", "Fatboy Slim"), std::make_pair("title", "The Rockafeller Skank") }); @@ -144,12 +155,15 @@ _event.data inside the state machine. In QML: \code - stateMacine.tap({ + stateMachine.submitEvent("tap", { "artist": "Fatboy Slim" "title": "The Rockafeller Skank" }) \endcode - Any invoked state machine with a name property will also show up as a - property on its parent state machine. + \note A state machine needs a \c QEventLoop to work correctly. The event loop is used + to implement the \c delay attribute for events and to schedule the processing of a state machine + when events are received from nested (or parent) state machines. A QML application or a widget + application will always have an event loop running, so nothing extra is needed. For other + applications, \c QApplication::run will have to be called to start the event loop processing. */ diff --git a/src/scxml/doc/qtscxml-module-qml.qdoc b/src/scxml/doc/qtscxml-module-qml.qdoc index ddcdf3e..e549f48 100644 --- a/src/scxml/doc/qtscxml-module-qml.qdoc +++ b/src/scxml/doc/qtscxml-module-qml.qdoc @@ -26,7 +26,7 @@ ****************************************************************************/ /*! - \qmlmodule Scxml 5.7 + \qmlmodule QtScxml 5.8 \title Qt SCXML QML Types \ingroup qmlmodules \brief Enables the use of SCXML state machines with QML. @@ -35,7 +35,7 @@ in your .qml file: \code - import QtScxml 5.7 + import QtScxml 5.8 \endcode For more information, see \l{Instantiating State Machines}. diff --git a/src/scxml/doc/qtscxml-overview.qdoc b/src/scxml/doc/qtscxml-overview.qdoc index 8b38e39..dfecb0a 100644 --- a/src/scxml/doc/qtscxml-overview.qdoc +++ b/src/scxml/doc/qtscxml-overview.qdoc @@ -90,26 +90,6 @@ differ in the way they are instantiated. For more information, see \l{Instantiating State Machines} and \l{Qt SCXML Examples}. - \section1 Qt Mode - - State machines that are compatible with Qt can be compiled or loaded in the - \e {Qt mode} to fully benefit from Qt. The Qt mode is unconditionally - enabled by adding the following line to the beginning of the SCXML file: - \c {<!-- enable-qt-mode: yes -->}. It is unconditionally disabled by the - value \c no. - - The Qt mode should be disabled for random SCXML files that do not comply to - the Qt rules. For example, for an SCXML file to be compatible with the Qt - mode, all IDs and names must be valid Qt identifiers. - - In the Qt mode, external signals defined inside an SCXML file that are of - the \c qt:signal type, are accessible as signals of QScxmlStateMachine. As - an argument, they take QVariant. It is of QMap<QString, QVariant> type and - contains the content of all the \c <param> elements specified as children of - a \c <send> element. The name of each QScxmlStateMachine signal corresponds - to the value defined in the \e event attribute of one \c <send> tag in the - SCXML file. - \section1 Logging Categories The Qt SCXML module exports the following logging categories: diff --git a/src/scxml/doc/qtscxml-scxml-compliance.qdoc b/src/scxml/doc/qtscxml-scxml-compliance.qdoc index e86feca..ab1d730 100644 --- a/src/scxml/doc/qtscxml-scxml-compliance.qdoc +++ b/src/scxml/doc/qtscxml-scxml-compliance.qdoc @@ -72,19 +72,6 @@ The Qt SCXML implementation extends SCXML in the following ways: \list - \li For communication purposes, the \c <send> tag supports an extra - value \c "qt:signal" accepted by the attribute \c type. - The generated state machine or the state machine that has loaded - the SCXML file dynamically will contain the corresponding - signal, the name of which will be equal to the value of the - \e event attribute inside the \c <send> tag. - All of the \c <param> children will be placed inside the - QMap<QString, QVariant> map, and this map will be passed as a - QVariant argument of the specified signal. - \li State machines that are compatible with Qt can be compiled or loaded in - the \e {Qt mode} to fully benefit from Qt. The Qt mode is enabled by - adding the following line to the beginning of the SCXML file: - \c {<!-- enable-qt-mode: yes -->}. \li If the event is an error event, \c _event.errorMessage will contain a more detailed description of the error. \endlist diff --git a/src/scxml/doc/qtscxml.qdocconf b/src/scxml/doc/qtscxml.qdocconf index d7e5f1a..0e794d9 100644 --- a/src/scxml/doc/qtscxml.qdocconf +++ b/src/scxml/doc/qtscxml.qdocconf @@ -34,7 +34,7 @@ qhp.QtScxml.virtualFolder = qtscxml qhp.QtScxml.indexTitle = Qt SCXML qhp.QtScxml.indexRoot = -depends += qtcore qtdeclarative qtdoc qmake qtwidgets +depends += qtcore qtdoc qmake qtquick qtwidgets headerdirs = .. ../../imports/scxmlstatemachine sourcedirs += .. \ diff --git a/src/scxml/qscxmlparser.cpp b/src/scxml/qscxmlcompiler.cpp index 3906ada..691f87a 100644 --- a/src/scxml/qscxmlparser.cpp +++ b/src/scxml/qscxmlcompiler.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ -#include "qscxmlparser_p.h" +#include "qscxmlcompiler_p.h" #include "qscxmlexecutablecontent_p.h" #include <QXmlStreamReader> @@ -49,12 +49,12 @@ #include <QString> #ifndef BUILD_QSCXMLC -#include "qscxmlnulldatamodel.h" #include "qscxmlecmascriptdatamodel.h" -#include "qscxmlqstates.h" +#include "qscxmlinvokableservice_p.h" #include "qscxmldatamodel_p.h" #include "qscxmlstatemachine_p.h" #include "qscxmlstatemachine.h" +#include "qscxmltabledata_p.h" #include <QState> #include <QHistoryState> @@ -96,12 +96,12 @@ public: doc->isVerified = true; m_doc = doc; - foreach (DocumentModel::AbstractState *state, doc->allStates) { + for (DocumentModel::AbstractState *state : qAsConst(doc->allStates)) { if (state->id.isEmpty()) { continue; #ifndef QT_NO_DEBUG } else if (m_stateById.contains(state->id)) { - Q_ASSERT(!"Should be unreachable: the parser should check for this case!"); + Q_ASSERT(!"Should be unreachable: the compiler should check for this case!"); #endif // QT_NO_DEBUG } else { m_stateById[state->id] = state; @@ -116,7 +116,6 @@ public: private: bool visit(DocumentModel::Scxml *scxml) Q_DECL_OVERRIDE { - Q_ASSERT(scxml->initialStates.isEmpty()); if (!scxml->name.isEmpty() && !isValidToken(scxml->name, XmlNmtoken)) { error(scxml->xmlLocation, QStringLiteral("scxml name '%1' is not a valid XML Nmtoken").arg(scxml->name)); @@ -124,15 +123,17 @@ private: if (scxml->initial.isEmpty()) { if (auto firstChild = firstAbstractState(scxml)) { - scxml->initialStates.append(firstChild); + scxml->initialTransition = createInitialTransition({firstChild}); } } else { - foreach (const QString &initial, scxml->initial) { + QVector<DocumentModel::AbstractState *> initialStates; + for (const QString &initial : qAsConst(scxml->initial)) { if (DocumentModel::AbstractState *s = m_stateById.value(initial)) - scxml->initialStates.append(s); + initialStates.append(s); else error(scxml->xmlLocation, QStringLiteral("initial state '%1' not found for <scxml> element").arg(initial)); } + scxml->initialTransition = createInitialTransition(initialStates); } m_parentNodes.append(scxml); @@ -147,28 +148,41 @@ private: bool visit(DocumentModel::State *state) Q_DECL_OVERRIDE { - Q_ASSERT(state->initialStates.isEmpty()); - if (!state->id.isEmpty() && !isValidToken(state->id, XmlNCName)) { error(state->xmlLocation, QStringLiteral("'%1' is not a valid XML ID").arg(state->id)); - } else if (state->type != DocumentModel::State::Initial) { - validateStateName(state->xmlLocation, state->id); } - if (state->initial.isEmpty()) { - if (auto firstChild = firstAbstractState(state)) { - state->initialStates += firstChild; - } - } else { - Q_ASSERT(state->type == DocumentModel::State::Normal); - foreach (const QString &initialState, state->initial) { - if (DocumentModel::AbstractState *s = m_stateById.value(initialState)) { - state->initialStates += s; + if (state->initialTransition == nullptr) { + if (state->initial.isEmpty()) { + if (state->type == DocumentModel::State::Parallel) { + auto allChildren = allAbstractStates(state); + state->initialTransition = createInitialTransition(allChildren); } else { - error(state->xmlLocation, - QStringLiteral("undefined initial state '%1' for state '%2'") - .arg(initialState, state->id)); + if (auto firstChild = firstAbstractState(state)) { + state->initialTransition = createInitialTransition({firstChild}); + } + } + } else { + Q_ASSERT(state->type == DocumentModel::State::Normal); + QVector<DocumentModel::AbstractState *> initialStates; + for (const QString &initialState : qAsConst(state->initial)) { + if (DocumentModel::AbstractState *s = m_stateById.value(initialState)) { + initialStates.append(s); + } else { + error(state->xmlLocation, + QStringLiteral("undefined initial state '%1' for state '%2'") + .arg(initialState, state->id)); + } } + state->initialTransition = createInitialTransition(initialStates); + } + } else { + if (state->initial.isEmpty()) { + visit(state->initialTransition); + } else { + error(state->xmlLocation, + QStringLiteral("initial transition and initial attribute for state '%1'") + .arg(state->id)); } } @@ -177,27 +191,8 @@ private: break; case DocumentModel::State::Parallel: if (!state->initial.isEmpty()) { - error(state->xmlLocation, QStringLiteral("parallel states cannot have an initial state")); - } - break; - case DocumentModel::State::Initial: - if (transitionCount(state) != 1) - error(state->xmlLocation, QStringLiteral("an initial state can only have one transition, but has '%1'").arg(transitionCount(state))); - if (DocumentModel::Transition *t = firstTransition(state)) { - if (!t->events.isEmpty() || !t->condition.isNull()) { - error(t->xmlLocation, QStringLiteral("the transition in an initial state cannot have an event or a condition")); - } - if (t->targets.isEmpty()) { - error(t->xmlLocation, QStringLiteral("the transition in an initial state must have at least one target")); - } - } - foreach (DocumentModel::StateOrTransition *child, state->children) { - if (DocumentModel::State *s = child->asState()) { - error(s->xmlLocation, QStringLiteral("substates are not allowed in initial states")); - } - } - if (parentState() == Q_NULLPTR) { - error(state->xmlLocation, QStringLiteral("initial states can only occur in a state")); + error(state->xmlLocation, + QStringLiteral("parallel states cannot have an initial state")); } break; case DocumentModel::State::Final: @@ -221,7 +216,7 @@ private: if (int size = transition->targets.size()) transition->targetStates.reserve(size); - foreach (const QString &target, transition->targets) { + for (const QString &target : qAsConst(transition->targets)) { if (DocumentModel::AbstractState *s = m_stateById.value(target)) { if (transition->targetStates.contains(s)) { error(transition->xmlLocation, QStringLiteral("duplicate target '%1'").arg(target)); @@ -232,15 +227,9 @@ private: error(transition->xmlLocation, QStringLiteral("unknown state '%1' in target").arg(target)); } } - foreach (const QString &event, transition->events) { + for (const QString &event : qAsConst(transition->events)) checkEvent(event, transition->xmlLocation, AllowWildCards); - if (!DocumentModel::isEventToBeGenerated(event)) - continue; - - validateEventName(transition->xmlLocation, event); - } - m_parentNodes.append(transition); return true; } @@ -253,7 +242,7 @@ private: bool visit(DocumentModel::HistoryState *state) Q_DECL_OVERRIDE { bool seenTransition = false; - foreach (DocumentModel::StateOrTransition *sot, state->children) { + for (DocumentModel::StateOrTransition *sot : qAsConst(state->children)) { if (DocumentModel::State *s = sot->asState()) { error(s->xmlLocation, QStringLiteral("history state cannot have substates")); } else if (DocumentModel::Transition *t = sot->asTransition()) { @@ -274,11 +263,6 @@ private: bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE { checkEvent(node->event, node->xmlLocation, ForbidWildCards); - - if (node->type == QStringLiteral("qt:signal")) { - validateEventName(node->xmlLocation, node->event); - } - checkExpr(node->xmlLocation, QStringLiteral("send"), QStringLiteral("eventexpr"), node->eventexpr); return true; } @@ -296,6 +280,9 @@ private: bool visit(DocumentModel::Invoke *node) Q_DECL_OVERRIDE { + if (!node->srcexpr.isEmpty()) + return false; + if (node->content.isNull()) { error(node->xmlLocation, QStringLiteral("no valid content found in <invoke> tag")); } else { @@ -409,321 +396,72 @@ private: return true; } - static int transitionCount(DocumentModel::State *state) - { - int count = 0; - foreach (DocumentModel::StateOrTransition *child, state->children) { - if (child->asTransition()) - ++count; - } - return count; - } - - static DocumentModel::Transition *firstTransition(DocumentModel::State *state) - { - foreach (DocumentModel::StateOrTransition *child, state->children) { - if (DocumentModel::Transition *t = child->asTransition()) - return t; - } - return Q_NULLPTR; - } - - static DocumentModel::AbstractState *firstAbstractState(DocumentModel::StateContainer *container) + static const QVector<DocumentModel::StateOrTransition *> &allChildrenOfContainer( + DocumentModel::StateContainer *container) { - QVector<DocumentModel::StateOrTransition *> children; if (auto state = container->asState()) - children = state->children; + return state->children; else if (auto scxml = container->asScxml()) - children = scxml->children; + return scxml->children; else Q_UNREACHABLE(); - foreach (DocumentModel::StateOrTransition *child, children) { - if (DocumentModel::State *s = child->asState()) - return s; - else if (DocumentModel::HistoryState *h = child->asHistoryState()) - return h; - } - return Q_NULLPTR; - } - - void checkExpr(const DocumentModel::XmlLocation &loc, const QString &tag, const QString &attrName, const QString &attrValue) - { - if (m_doc->root->dataModel == DocumentModel::Scxml::NullDataModel && !attrValue.isEmpty()) { - error(loc, QStringLiteral( - "%1 in <%2> cannot be used with data model 'null'").arg(attrName, tag)); - } } - void error(const DocumentModel::XmlLocation &location, const QString &message) + static DocumentModel::AbstractState *firstAbstractState(DocumentModel::StateContainer *container) { - m_hasErrors = true; - if (m_errorHandler) - m_errorHandler(location, message); - } + const auto &allChildren = allChildrenOfContainer(container); - DocumentModel::Node *parentState() const - { - for (int i = m_parentNodes.size() - 1; i >= 0; --i) { - if (DocumentModel::State *s = m_parentNodes.at(i)->asState()) + QVector<DocumentModel::AbstractState *> childStates; + for (DocumentModel::StateOrTransition *child : qAsConst(allChildren)) { + if (DocumentModel::State *s = child->asState()) return s; + else if (DocumentModel::HistoryState *h = child->asHistoryState()) + return h; } - - return Q_NULLPTR; + return nullptr; } - void validateEventName(const DocumentModel::XmlLocation &location, const QString &name) + static QVector<DocumentModel::AbstractState *> allAbstractStates( + DocumentModel::StateContainer *container) { - if (!m_doc->qtMode) - return; - - if (!isValidCppIdentifier(name)) - error(location, QStringLiteral( - "event name '%1' is not a valid C++ identifier in Qt mode").arg(name)); - - if (!isValidQtIdentifier(name)) - error(location, QStringLiteral( - "event name '%1' is not a valid Qt identifier in Qt mode").arg(name)); - - if (m_stateById.contains(name)) - error(location, QStringLiteral( - "event name '%1' collides with a state name '%1' in Qt mode").arg(name)); + const auto &allChildren = allChildrenOfContainer(container); - const QString changedSuffix(QStringLiteral("Changed")); - if (name.endsWith(changedSuffix)) { - const QString baseName = name.left(name.count() - changedSuffix.count()); - if (m_stateById.contains(baseName)) - error(location, QStringLiteral( - "event name '%1' collides with a state name '%2' in Qt mode").arg(name).arg(baseName)); + QVector<DocumentModel::AbstractState *> childStates; + for (DocumentModel::StateOrTransition *child : qAsConst(allChildren)) { + if (DocumentModel::State *s = child->asState()) + childStates.append(s); + else if (DocumentModel::HistoryState *h = child->asHistoryState()) + childStates.append(h); } + return childStates; } - void validateStateName(const DocumentModel::XmlLocation &location, const QString &name) + DocumentModel::Transition *createInitialTransition( + const QVector<DocumentModel::AbstractState *> &states) { - if (!m_doc->qtMode) - return; - - if (!isValidCppIdentifier(name)) - error(location, QStringLiteral( - "state name '%1' is not a valid C++ identifier in Qt mode").arg(name)); - - if (!isValidQtIdentifier(name)) - error(location, QStringLiteral( - "state name '%1' is not a valid Qt identifier in Qt mode").arg(name)); - - const QString changedSuffix(QStringLiteral("Changed")); - if (name.endsWith(changedSuffix)) { - const QString baseName = name.left(name.count() - changedSuffix.count()); - if (m_stateById.contains(baseName)) - error(location, QStringLiteral( - "state name '%1' collides with a state name '%2' in Qt mode").arg(name).arg(baseName)); + auto *newTransition = m_doc->newTransition(nullptr, DocumentModel::XmlLocation(-1, -1)); + newTransition->type = DocumentModel::Transition::Synthetic; + for (auto *s : states) { + newTransition->targets.append(s->id); } + + newTransition->targetStates = states; + return newTransition; } - static bool isValidCppIdentifier(const QString &str) + void checkExpr(const DocumentModel::XmlLocation &loc, const QString &tag, const QString &attrName, const QString &attrValue) { - static const QStringList keywords = QStringList() - << QStringLiteral("alignas") - << QStringLiteral("alignof") - << QStringLiteral("asm") - << QStringLiteral("auto") - << QStringLiteral("bool") - << QStringLiteral("break") - << QStringLiteral("case") - << QStringLiteral("catch") - << QStringLiteral("char") - << QStringLiteral("char16_t") - << QStringLiteral("char32_t") - << QStringLiteral("class") - << QStringLiteral("const") - << QStringLiteral("constexpr") - << QStringLiteral("const_cast") - << QStringLiteral("continue") - << QStringLiteral("decltype") - << QStringLiteral("default") - << QStringLiteral("delete") - << QStringLiteral("double") - << QStringLiteral("do") - << QStringLiteral("dynamic_cast") - << QStringLiteral("else") - << QStringLiteral("enum") - << QStringLiteral("explicit") - << QStringLiteral("export") - << QStringLiteral("extern") - << QStringLiteral("false") - << QStringLiteral("float") - << QStringLiteral("for") - << QStringLiteral("friend") - << QStringLiteral("goto") - << QStringLiteral("if") - << QStringLiteral("inline") - << QStringLiteral("int") - << QStringLiteral("long") - << QStringLiteral("mutable") - << QStringLiteral("namespace") - << QStringLiteral("new") - << QStringLiteral("noexcept") - << QStringLiteral("nullptr") - << QStringLiteral("operator") - << QStringLiteral("private") - << QStringLiteral("protected") - << QStringLiteral("public") - << QStringLiteral("register") - << QStringLiteral("reinterpret_cast") - << QStringLiteral("return") - << QStringLiteral("short") - << QStringLiteral("signed") - << QStringLiteral("sizeof") - << QStringLiteral("static") - << QStringLiteral("static_assert") - << QStringLiteral("static_cast") - << QStringLiteral("struct") - << QStringLiteral("switch") - << QStringLiteral("template") - << QStringLiteral("this") - << QStringLiteral("thread_local") - << QStringLiteral("throw") - << QStringLiteral("true") - << QStringLiteral("try") - << QStringLiteral("typedef") - << QStringLiteral("typeid") - << QStringLiteral("typename") - << QStringLiteral("union") - << QStringLiteral("unsigned") - << QStringLiteral("using") - << QStringLiteral("virtual") - << QStringLiteral("void") - << QStringLiteral("volatile") - << QStringLiteral("wchar_t") - << QStringLiteral("while"); - - if (keywords.contains(str)) { - return false; - } - - auto isNonDigit = [](QChar ch) -> bool { - return (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z')) - || (ch >= QLatin1Char('a') && ch <= QLatin1Char('z')) - || (ch == QLatin1Char('_')); - }; - - QChar ch = str.at(0); - if (!isNonDigit(ch)) { - return false; - } - for (int i = 1, ei = str.size(); i != ei; ++i) { - ch = str.at(i); - if (!isNonDigit(ch) && !ch.isDigit()) { - return false; - } - } - - if (str.startsWith(QLatin1Char('_')) && str.size() > 1) { - QChar ch = str.at(1); - if (ch == QLatin1Char('_') - || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z'))) { - return false; - } + if (m_doc->root->dataModel == DocumentModel::Scxml::NullDataModel && !attrValue.isEmpty()) { + error(loc, QStringLiteral( + "%1 in <%2> cannot be used with data model 'null'").arg(attrName, tag)); } - - return true; } - static bool isValidQtIdentifier(const QString &str) + void error(const DocumentModel::XmlLocation &location, const QString &message) { - static const QStringList keywords = QStringList() - << QStringLiteral("QObject") - << QStringLiteral("event") - << QStringLiteral("eventFilter") - << QStringLiteral("tr") - << QStringLiteral("trUtf8") - << QStringLiteral("metaObject") - << QStringLiteral("staticMetaObject") - << QStringLiteral("objectName") - << QStringLiteral("setObjectName") - << QStringLiteral("isWidgetType") - << QStringLiteral("isWindowType") - << QStringLiteral("signalsBlocked") - << QStringLiteral("blockSignals") - << QStringLiteral("thread") - << QStringLiteral("moveToThread") - << QStringLiteral("startTimer") - << QStringLiteral("killTimer") - << QStringLiteral("findChild") - << QStringLiteral("findChildren") - << QStringLiteral("children") - << QStringLiteral("setParent") - << QStringLiteral("installEventFilter") - << QStringLiteral("removeEventFilter") - << QStringLiteral("connect") - << QStringLiteral("connect_functor") - << QStringLiteral("disconnect") - << QStringLiteral("dumpObjectTree") - << QStringLiteral("dumpObjectInfo") - << QStringLiteral("setProperty") - << QStringLiteral("property") - << QStringLiteral("dynamicPropertyNames") - << QStringLiteral("registerUserData") - << QStringLiteral("setUserData") - << QStringLiteral("userData") - << QStringLiteral("destroyed") - << QStringLiteral("objectNameChanged") - << QStringLiteral("parent") - << QStringLiteral("inherits") - << QStringLiteral("deleteLater") - << QStringLiteral("sender") - << QStringLiteral("senderSignalIndex") - << QStringLiteral("receivers") - << QStringLiteral("isSignalConnected") - << QStringLiteral("timerEvent") - << QStringLiteral("childEvent") - << QStringLiteral("customEvent") - << QStringLiteral("connectNotify") - << QStringLiteral("disconnectNotify") - << QStringLiteral("d_ptr") - << QStringLiteral("staticQtMetaObject") - << QStringLiteral("d_func") - << QStringLiteral("connectImpl") - << QStringLiteral("disconnectImpl") - - << QStringLiteral("QScxmlStateMachine") - << QStringLiteral("running") - << QStringLiteral("BindingMethod") - << QStringLiteral("EarlyBinding") - << QStringLiteral("LateBinding") - << QStringLiteral("fromFile") - << QStringLiteral("fromData") - << QStringLiteral("parseErrors") - << QStringLiteral("sessionId") - << QStringLiteral("setSessionId") - << QStringLiteral("generateSessionId") - << QStringLiteral("isInvoked") - << QStringLiteral("dataModel") - << QStringLiteral("dataBinding") - << QStringLiteral("init") - << QStringLiteral("isRunning") - << QStringLiteral("name") - << QStringLiteral("stateNames") - << QStringLiteral("activeStateNames") - << QStringLiteral("isActive") - << QStringLiteral("scxmlEventFilter") - << QStringLiteral("setScxmlEventFilter") - << QStringLiteral("submitEvent") - << QStringLiteral("cancelDelayedEvent") - << QStringLiteral("isDispatchableTarget") - << QStringLiteral("runningChanged") - << QStringLiteral("log") - << QStringLiteral("reachedStableState") - << QStringLiteral("finished") - << QStringLiteral("eventOccurred") - << QStringLiteral("start") - << QStringLiteral("setDataBinding") - << QStringLiteral("setService") - << QStringLiteral("tableData"); - - if (keywords.contains(str)) - return false; - - return true; + m_hasErrors = true; + if (m_errorHandler) + m_errorHandler(location, message); } private: @@ -735,22 +473,48 @@ private: }; #ifndef BUILD_QSCXMLC -class QStateMachineBuilder; -class DynamicStateMachine: public QScxmlStateMachine, public QScxmlEventFilter +class InvokeDynamicScxmlFactory: public QScxmlScxmlServiceFactory +{ +public: + InvokeDynamicScxmlFactory(const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &namelist, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶ms) + : QScxmlScxmlServiceFactory(invokeInfo, namelist, params) + {} + + void setContent(const QSharedPointer<DocumentModel::ScxmlDocument> &content) + { m_content = content; } + + QScxmlInvokableService *invoke(QScxmlStateMachine *child) Q_DECL_OVERRIDE; + +private: + QSharedPointer<DocumentModel::ScxmlDocument> m_content; +}; + +class DynamicStateMachinePrivate : public QScxmlStateMachinePrivate +{ +public: + DynamicStateMachinePrivate() : + QScxmlStateMachinePrivate(&QScxmlStateMachine::staticMetaObject) {} +}; + +class DynamicStateMachine: public QScxmlStateMachine, public QScxmlInternal::GeneratedTableData { + Q_DECLARE_PRIVATE(DynamicStateMachine) // Manually expanded from Q_OBJECT macro: public: Q_OBJECT_CHECK const QMetaObject *metaObject() const Q_DECL_OVERRIDE - { return m_metaObject; } + { return d_func()->m_metaObject; } int qt_metacall(QMetaObject::Call _c, int _id, void **_a) Q_DECL_OVERRIDE { + Q_D(DynamicStateMachine); _id = QScxmlStateMachine::qt_metacall(_c, _id, _a); if (_id < 0) return _id; - int ownMethodCount = m_metaObject->methodCount() - m_metaObject->methodOffset(); + int ownMethodCount = d->m_metaObject->methodCount() - d->m_metaObject->methodOffset(); if (_c == QMetaObject::InvokeMetaMethod) { if (_id < ownMethodCount) qt_static_metacall(this, _c, _id, _a); @@ -758,7 +522,7 @@ public: } else if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) { qt_static_metacall(this, _c, _id, _a); - _id -= m_metaObject->propertyCount(); + _id -= d->m_metaObject->propertyCount(); } return _id; } @@ -766,89 +530,40 @@ public: private: static void qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { - if (_c == QMetaObject::InvokeMetaMethod) { - DynamicStateMachine *_t = static_cast<DynamicStateMachine *>(_o); - if (_id >= _t->m_eventNamesByIndex.size() || _id < 0) { - // out of bounds - return; - } - if (_id >= _t->m_firstSubStateMachineSignal && _id < _t->m_firstSlot) { - // these signals are only emitted, not activated by another signal - return; - } - if (_id >= _t->m_firstStateChangedSignal && _id < _t->m_firstSubStateMachineSignal) { - // re-propagate QAbstractState::activeChanged as stateChanged - QMetaObject::activate(_t, _t->m_metaObject, _id, _a); - return; - } - // We have 1 kind of slots: those to submit events. - const QString &event = _t->m_eventNamesByIndex.at(_id); - if (!event.isEmpty()) { - if (_id < _t->m_firstSlotWithoutData) { - QVariant data = *reinterpret_cast< QVariant(*)>(_a[1]); - if (data.canConvert<QJSValue>()) { - data = data.value<QJSValue>().toVariant(); - } - _t->submitEvent(event, data); - } else { - _t->submitEvent(event, QVariant()); - } - } - } else if (_c == QMetaObject::RegisterPropertyMetaType) { - DynamicStateMachine *_t = static_cast<DynamicStateMachine *>(_o); - if (_id < _t->m_firstSubStateMachineProperty) { - *reinterpret_cast<int*>(_a[0]) = qRegisterMetaType<bool>(); - } else { - *reinterpret_cast<int*>(_a[0]) = qRegisterMetaType<QScxmlStateMachine *>(); - } + if (_c == QMetaObject::RegisterPropertyMetaType) { + *reinterpret_cast<int*>(_a[0]) = qRegisterMetaType<bool>(); } else if (_c == QMetaObject::ReadProperty) { DynamicStateMachine *_t = static_cast<DynamicStateMachine *>(_o); void *_v = _a[0]; - if (_id >= 0 && _id < _t->m_propertyNamesByIndex.size()) { - if (_id < _t->m_firstSubStateMachineProperty) { - // getter for the state - auto smp = QScxmlStateMachinePrivate::get(_t); - auto name = _t->m_propertyNamesByIndex.at(_id); - *reinterpret_cast<bool*>(_v) = smp->stateByScxmlName(name)->active(); - } else { - // getter for a child statemachine - int idx = _id - _t->m_firstSubStateMachineProperty; - *reinterpret_cast<QScxmlStateMachine **>(_v) = _t->m_subStateMachines.at(idx); - } + if (_id >= 0 && _id < _t->m_propertyCount) { + // getter for the state + *reinterpret_cast<bool*>(_v) = _t->isActive(_id); } } } // end of Q_OBJECT macro private: - friend QStateMachineBuilder; DynamicStateMachine() - : m_metaObject(Q_NULLPTR) - , m_firstSubStateMachineSignal(0) - , m_firstSlot(0) - , m_firstSlotWithoutData(0) - , m_firstSubStateMachineProperty(0) + : QScxmlStateMachine(*new DynamicStateMachinePrivate) + , m_propertyCount(0) { - // Temporarily wire up the QMetaObject, because qobject_cast needs it while building MyQStateMachine. + // Temporarily wire up the QMetaObject + Q_D(DynamicStateMachine); QMetaObjectBuilder b; b.setClassName("DynamicStateMachine"); b.setSuperClass(&QScxmlStateMachine::staticMetaObject); b.setStaticMetacallFunction(qt_static_metacall); - m_metaObject = b.toMetaObject(); - - setScxmlEventFilter(this); + d->m_metaObject = b.toMetaObject(); } - void initDynamicParts(const QSet<QString> &eventSignals, - const QSet<QString> &eventSlots, - const QList<QString> &stateNames, - const QList<QString> &subStateMachineNames) + void initDynamicParts(const MetaDataInfo &info) { + Q_D(DynamicStateMachine); // Release the temporary QMetaObject. - Q_ASSERT(m_metaObject); - free(m_metaObject); - - m_eventNamesByIndex.reserve(eventSignals.size() + subStateMachineNames.size() + eventSlots.size()); + Q_ASSERT(d->m_metaObject != &QScxmlStateMachine::staticMetaObject); + free(const_cast<QMetaObject *>(d->m_metaObject)); + d->m_metaObject = &QScxmlStateMachine::staticMetaObject; // Build the real one. QMetaObjectBuilder b; @@ -857,126 +572,60 @@ private: b.setStaticMetacallFunction(qt_static_metacall); // signals - foreach (const QString &eventName, eventSignals) { - QByteArray signalName = eventName.toUtf8() + "(const QVariant &)"; - QMetaMethodBuilder signalBuilder = b.addSignal(signalName); - signalBuilder.setParameterNames(init("data")); - int idx = signalBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - m_eventNamesByIndex[idx] = eventName; - } - - m_firstStateChangedSignal = m_eventNamesByIndex.size(); - foreach (const QString &stateName, stateNames) { + for (const QString &stateName : info.stateNames) { auto name = stateName.toUtf8(); - QByteArray signalName = name + "Changed(bool)"; + const QByteArray signalName = name + "Changed(bool)"; QMetaMethodBuilder signalBuilder = b.addSignal(signalName); signalBuilder.setParameterNames(init("active")); - int idx = signalBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - } - - m_firstSubStateMachineSignal = m_eventNamesByIndex.size(); - foreach (const QString &machineName, subStateMachineNames) { - auto name = machineName.toUtf8(); - QByteArray signalName = name + "Changed(QScxmlStateMachine *)"; - QMetaMethodBuilder signalBuilder = b.addSignal(signalName); - signalBuilder.setParameterNames(init("statemachine")); - int idx = signalBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - } - - // slots - m_firstSlot = m_eventNamesByIndex.size(); - foreach (const QString &eventName, eventSlots) { - QByteArray slotName = eventName.toUtf8() + "(const QVariant &)"; - QMetaMethodBuilder slotBuilder = b.addSlot(slotName); - slotBuilder.setParameterNames(init("data")); - int idx = slotBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - m_eventNamesByIndex[idx] = eventName; - } - - m_firstSlotWithoutData = m_eventNamesByIndex.size(); - foreach (const QString &eventName, eventSlots) { - QByteArray slotName = eventName.toUtf8() + "()"; - QMetaMethodBuilder slotBuilder = b.addSlot(slotName); - int idx = slotBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - m_eventNamesByIndex[idx] = eventName; } // properties - int stateNotifier = m_firstStateChangedSignal; - foreach (const QString &stateName, stateNames) { - QMetaPropertyBuilder prop = b.addProperty(stateName.toUtf8(), "bool", stateNotifier); + int notifier = 0; + for (const QString &stateName : info.stateNames) { + QMetaPropertyBuilder prop = b.addProperty(stateName.toUtf8(), "bool", notifier); prop.setWritable(false); - int idx = prop.index(); - m_propertyNamesByIndex.resize(std::max(idx + 1, m_propertyNamesByIndex.size())); - m_propertyNamesByIndex[idx] = stateName; - ++stateNotifier; - } - - m_firstSubStateMachineProperty = m_propertyNamesByIndex.size(); - int notifier = m_firstSubStateMachineSignal; - foreach (const QString &machineName, subStateMachineNames) { - QMetaPropertyBuilder prop = b.addProperty(machineName.toUtf8(), "QScxmlStateMachine *", notifier); - prop.setWritable(false); - int idx = prop.index(); - m_propertyNamesByIndex.resize(std::max(idx + 1, m_propertyNamesByIndex.size())); - m_propertyNamesByIndex[idx] = machineName; + ++m_propertyCount; ++notifier; } - m_subStateMachines.resize(subStateMachineNames.size()); // And we're done - m_metaObject = b.toMetaObject(); + d->m_metaObject = b.toMetaObject(); } public: ~DynamicStateMachine() - { if (m_metaObject) free(m_metaObject); } - - bool handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) Q_DECL_OVERRIDE { - Q_UNUSED(stateMachine); - - if (event->originType() != QStringLiteral("qt:signal")) { - return true; - } - - auto eventName = event->name(); - for (int i = 0; i < m_firstSubStateMachineSignal; ++i) { - if (m_eventNamesByIndex.at(i) == eventName) { - QVariant data = event->data(); - void *argv[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&data)) }; - QMetaObject::activate(this, metaObject(), i, argv); - return false; - } + { + Q_D(DynamicStateMachine); + if (d->m_metaObject != &QScxmlStateMachine::staticMetaObject) { + free(const_cast<QMetaObject *>(d->m_metaObject)); + d->m_metaObject = &QScxmlStateMachine::staticMetaObject; } - - return true; } -protected: - void setService(const QString &id, QScxmlInvokableService *service) Q_DECL_OVERRIDE + QScxmlInvokableServiceFactory *serviceFactory(int id) const Q_DECL_OVERRIDE Q_DECL_FINAL + { return m_allFactoriesById.at(id); } + + static DynamicStateMachine *build(DocumentModel::ScxmlDocument *doc) { - int idx = -1; - for (int i = m_firstSubStateMachineProperty, ei = m_propertyNamesByIndex.size(); i != ei; ++i) { - if (m_propertyNamesByIndex.at(i) == id) { - idx = i - m_firstSubStateMachineProperty; - break; - } - } - if (idx < 0) - return; - auto scxml = service ? dynamic_cast<QScxmlInvokableScxml *>(service) : Q_NULLPTR; - auto machine = scxml ? scxml->stateMachine() : Q_NULLPTR; - if (m_subStateMachines.at(idx) != machine) { - m_subStateMachines[idx] = machine; - // emit changed signal: - void *argv[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&machine)) }; - QMetaObject::activate(this, metaObject(), m_firstSubStateMachineSignal + idx, argv); - } + auto stateMachine = new DynamicStateMachine; + MetaDataInfo info; + DataModelInfo dm; + auto factoryIdCreator = [stateMachine]( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &namelist, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶ms, + const QSharedPointer<DocumentModel::ScxmlDocument> &content) -> int { + auto factory = new InvokeDynamicScxmlFactory(invokeInfo, namelist, params); + factory->setContent(content); + stateMachine->m_allFactoriesById.append(factory); + return stateMachine->m_allFactoriesById.size() - 1; + }; + + GeneratedTableData::build(doc, stateMachine, &info, &dm, factoryIdCreator); + stateMachine->setTableData(stateMachine); + stateMachine->initDynamicParts(info); + + return stateMachine; } private: @@ -990,438 +639,106 @@ private: } private: - QMetaObject *m_metaObject; - QVector<QString> m_eventNamesByIndex; - QVector<QString> m_propertyNamesByIndex; - QVector<QScxmlStateMachine *> m_subStateMachines; - int m_firstSubStateMachineSignal; - int m_firstStateChangedSignal; - int m_firstSlot; - int m_firstSlotWithoutData; - int m_firstSubStateMachineProperty; + QVector<QScxmlInvokableServiceFactory *> m_allFactoriesById; + int m_propertyCount; }; -class InvokeDynamicScxmlFactory: public QScxmlInvokableScxmlServiceFactory +inline QScxmlInvokableService *InvokeDynamicScxmlFactory::invoke( + QScxmlStateMachine *parentStateMachine) { -public: - InvokeDynamicScxmlFactory(QScxmlExecutableContent::StringId invokeLocation, - QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, - QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, - bool autoforward, - const QVector<Param> ¶ms, - QScxmlExecutableContent::ContainerId finalize) - : QScxmlInvokableScxmlServiceFactory(invokeLocation, id, idPrefix, idlocation, namelist, autoforward, params, finalize) - {} - - void setContent(const QSharedPointer<DocumentModel::ScxmlDocument> &content) - { m_content = content; } - - QScxmlInvokableService *invoke(QScxmlStateMachine *child) Q_DECL_OVERRIDE; - -private: - QSharedPointer<DocumentModel::ScxmlDocument> m_content; -}; - -class QStateMachineBuilder: public QScxmlExecutableContent::Builder -{ -public: - QStateMachineBuilder() - : m_stateMachine(Q_NULLPTR) - , m_currentTransition(Q_NULLPTR) - , m_bindLate(false) - , m_qtMode(false) - {} - - QScxmlStateMachine *build(DocumentModel::ScxmlDocument *doc) - { - m_stateMachine = Q_NULLPTR; - m_parents.reserve(32); - m_allTransitions.reserve(doc->allTransitions.size()); - m_docStatesToQStates.reserve(doc->allStates.size()); - m_qtMode = doc->qtMode; - - doc->root->accept(this); - wireTransitions(); - applyInitialStates(); - - QScxmlExecutableContent::DynamicTableData *td = tableData(); - td->setParent(m_stateMachine); - m_stateMachine->setTableData(td); - m_stateMachine->initDynamicParts(m_eventSignals, m_eventSlots, m_stateNames.keys(), m_subStateMachineNames.toList()); - - const auto signalCode = QByteArray::number(QSIGNAL_CODE); - for (auto it = m_stateNames.constBegin(), eit = m_stateNames.constEnd(); it != eit; ++it) { - QByteArray signal = signalCode + it.key().toUtf8() + "Changed(bool)"; - QObject::connect(it.value(), SIGNAL(activeChanged(bool)), m_stateMachine, signal.constData()); - } - - m_parents.clear(); - m_allTransitions.clear(); - m_docStatesToQStates.clear(); - m_currentTransition = Q_NULLPTR; - - return m_stateMachine; - } - -private: - using NodeVisitor::visit; - using QScxmlExecutableContent::Builder::createContext; - - bool visit(DocumentModel::Scxml *node) Q_DECL_OVERRIDE - { - m_stateMachine = new DynamicStateMachine; - - switch (node->binding) { - case DocumentModel::Scxml::EarlyBinding: - m_stateMachine->setDataBinding(QScxmlStateMachine::EarlyBinding); - break; - case DocumentModel::Scxml::LateBinding: - m_stateMachine->setDataBinding(QScxmlStateMachine::LateBinding); - m_bindLate = true; - break; - default: - Q_UNREACHABLE(); - } - - setName(node->name); - - m_parents.append(QScxmlStateMachinePrivate::get(m_stateMachine)->m_qStateMachine); - visit(node->children); - - m_dataElements.append(node->dataElements); - if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { - setInitialSetup(startNewSequence()); - generate(m_dataElements); - if (node->script) { - node->script->accept(this); - } - visit(&node->initialSetup); - endSequence(); - } - - m_parents.removeLast(); - - foreach (auto initialState, node->initialStates) { - Q_ASSERT(initialState); - m_initialStates.append(qMakePair(QScxmlStateMachinePrivate::get(m_stateMachine)->m_qStateMachine, initialState)); - } - - return false; - } - - bool visit(DocumentModel::State *node) Q_DECL_OVERRIDE - { - QAbstractState *newState = Q_NULLPTR; - switch (node->type) { - case DocumentModel::State::Normal: { - auto s = new QScxmlState(currentParent()); - newState = s; - foreach (DocumentModel::AbstractState *initialState, node->initialStates) { - m_initialStates.append(qMakePair(s, initialState)); - } - } break; - case DocumentModel::State::Parallel: { - auto s = new QScxmlState(currentParent()); - s->setChildMode(QState::ParallelStates); - newState = s; - } break; - case DocumentModel::State::Initial: { - auto s = new QScxmlState(currentParent()); - currentParent()->setInitialState(s); - newState = s; - } break; - case DocumentModel::State::Final: { - auto s = new QScxmlFinalState(currentParent()); - newState = s; - s->setDoneData(generate(node->doneData)); - } break; - default: - Q_UNREACHABLE(); - } - - newState->setObjectName(node->id); - m_stateNames.insert(node->id, newState); - - m_docStatesToQStates.insert(node, newState); - m_parents.append(newState); - - if (!node->dataElements.isEmpty()) { - if (m_bindLate) { - qobject_cast<QScxmlState *>(newState)->setInitInstructions(startNewSequence()); - generate(node->dataElements); - endSequence(); - } else { - m_dataElements.append(node->dataElements); - } - } - - QScxmlExecutableContent::ContainerId onEntry = generate(node->onEntry); - QScxmlExecutableContent::ContainerId onExit = generate(node->onExit); - if (QScxmlState *s = qobject_cast<QScxmlState *>(newState)) { - s->setOnEntryInstructions(onEntry); - s->setOnExitInstructions(onExit); - if (!node->invokes.isEmpty()) { - QVector<QScxmlInvokableServiceFactory *> factories; - foreach (DocumentModel::Invoke *invoke, node->invokes) { - auto ctxt = createContext(QStringLiteral("invoke")); - QVector<QScxmlExecutableContent::StringId> namelist; - foreach (const QString &name, invoke->namelist) - namelist += addString(name); - QVector<QScxmlInvokableServiceFactory::Param> params; - foreach (DocumentModel::Param *param, invoke->params) { - QScxmlInvokableServiceFactory::Param p; - p.name = addString(param->name); - p.expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), param->expr); - p.location = addString(param->location); - params.append(p); - } - QScxmlExecutableContent::ContainerId finalize = QScxmlExecutableContent::NoInstruction; - if (!invoke->finalize.isEmpty()) { - finalize = startNewSequence(); - visit(&invoke->finalize); - endSequence(); - } - auto factory = new InvokeDynamicScxmlFactory(ctxt, - addString(invoke->id), - addString(node->id + QStringLiteral(".session-")), - addString(invoke->idLocation), - namelist, - invoke->autoforward, - params, - finalize); - factory->setContent(invoke->content); - factories.append(factory); - QString name = invoke->content->root->name; - if (!name.isEmpty()) { - m_subStateMachineNames.insert(name); - } - } - s->setInvokableServiceFactories(factories); - } - } else if (QScxmlFinalState *f = qobject_cast<QScxmlFinalState *>(newState)) { - f->setOnEntryInstructions(onEntry); - f->setOnExitInstructions(onExit); - } else { - Q_UNREACHABLE(); - } - - visit(node->children); - - m_parents.removeLast(); - return false; - } - - bool visit(DocumentModel::Transition *node) Q_DECL_OVERRIDE - { - if (m_qtMode) { - m_eventSlots.unite(node->events.toSet()); - } - - auto newTransition = new QScxmlTransition(node->events); - if (QHistoryState *parent = qobject_cast<QHistoryState*>(m_parents.last())) { - parent->setDefaultTransition(newTransition); - } else { - currentParent()->addTransition(newTransition); - } - - if (node->condition) { - auto cond = createEvaluatorBool(QStringLiteral("transition"), QStringLiteral("cond"), *node->condition.data()); - newTransition->setConditionalExpression(cond); - } - - switch (node->type) { - case DocumentModel::Transition::External: - newTransition->setTransitionType(QAbstractTransition::ExternalTransition); - break; - case DocumentModel::Transition::Internal: - newTransition->setTransitionType(QAbstractTransition::InternalTransition); - break; - default: - Q_UNREACHABLE(); - } - - m_allTransitions.insert(newTransition, node); - if (!node->instructionsOnTransition.isEmpty()) { - m_currentTransition = newTransition; - newTransition->setInstructionsOnTransition(startNewSequence()); - visit(&node->instructionsOnTransition); - endSequence(); - m_currentTransition = 0; - } - Q_ASSERT(newTransition->stateMachine()); - return false; - } - - bool visit(DocumentModel::HistoryState *state) Q_DECL_OVERRIDE - { - QHistoryState *newState = new QScxmlHistoryState(currentParent()); - switch (state->type) { - case DocumentModel::HistoryState::Shallow: - newState->setHistoryType(QHistoryState::ShallowHistory); - break; - case DocumentModel::HistoryState::Deep: - newState->setHistoryType(QHistoryState::DeepHistory); - break; - default: - Q_UNREACHABLE(); - } - - newState->setObjectName(state->id); - m_docStatesToQStates.insert(state, newState); - m_parents.append(newState); - return true; - } + bool ok = true; + auto srcexpr = d->calculateSrcexpr(parentStateMachine, &ok); + if (!ok) + return Q_NULLPTR; - void endVisit(DocumentModel::HistoryState *) Q_DECL_OVERRIDE - { - m_parents.removeLast(); - } + if (!srcexpr.isEmpty()) + return invokeDynamic(parentStateMachine, srcexpr); - bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE - { - if (m_qtMode && node->type == QStringLiteral("qt:signal")) { - m_eventSignals.insert(node->event); - } + auto childStateMachine = DynamicStateMachine::build(m_content.data()); - return QScxmlExecutableContent::Builder::visit(node); - } + auto dm = QScxmlDataModelPrivate::instantiateDataModel(m_content->root->dataModel); + dm->setParent(childStateMachine); + childStateMachine->setDataModel(dm); -private: // Utility methods - QState *currentParent() const - { - if (m_parents.isEmpty()) - return Q_NULLPTR; + return invokeStatic(childStateMachine, parentStateMachine); +} +#endif // BUILD_QSCXMLC - QState *parent = qobject_cast<QState*>(m_parents.last()); - Q_ASSERT(parent); - return parent; - } +} // anonymous namespace - void wireTransitions() - { - for (QHash<QAbstractTransition *, DocumentModel::Transition*>::const_iterator i = m_allTransitions.begin(), ei = m_allTransitions.end(); i != ei; ++i) { - QList<QAbstractState *> targets; - targets.reserve(i.value()->targets.size()); - foreach (DocumentModel::AbstractState *targetState, i.value()->targetStates) { - QAbstractState *target = m_docStatesToQStates.value(targetState); - Q_ASSERT(target); - targets.append(target); - } - i.key()->setTargetStates(targets); +#ifndef BUILD_QSCXMLC +QScxmlScxmlService *QScxmlScxmlServiceFactory::invokeDynamic( + QScxmlStateMachine *parentStateMachine, const QString &sourceUrl) +{ + QScxmlCompiler::Loader *loader = parentStateMachine->loader(); - if (DebugHelper_NameTransitions) - i.key()->setObjectName(QStringLiteral("%1 -> %2").arg(i.key()->parent()->objectName(), i.value()->targets.join(QStringLiteral(",")))); - } - } + const QString baseDir = sourceUrl.isEmpty() ? QString() : QFileInfo(sourceUrl).path(); + QStringList errs; + const QByteArray data = loader->load(sourceUrl, baseDir, &errs); - void applyInitialStates() - { - foreach (const auto &init, m_initialStates) { - Q_ASSERT(init.second); - auto initialState = m_docStatesToQStates.value(init.second); - Q_ASSERT(initialState); - init.first->setInitialState(initialState); - } + if (!errs.isEmpty()) { + qWarning() << errs; + return Q_NULLPTR; } - QString createContextString(const QString &instrName) const Q_DECL_OVERRIDE - { - if (m_currentTransition) { - QString state; - if (QState *s = m_currentTransition->sourceState()) { - state = QStringLiteral(" of state '%1'").arg(s->objectName()); - } - return QStringLiteral("%1 instruction in transition %2 %3").arg(instrName, m_currentTransition->objectName(), state); - } else { - return QStringLiteral("%1 instruction in state %2").arg(instrName, m_parents.last()->objectName()); - } + QXmlStreamReader reader(data); + QScxmlCompiler compiler(&reader); + compiler.setFileName(sourceUrl); + compiler.setLoader(parentStateMachine->loader()); + compiler.compile(); + if (!compiler.errors().isEmpty()) { + const auto errors = compiler.errors(); + for (const QScxmlError &error : errors) + qWarning() << error.toString(); + return Q_NULLPTR; } - QString createContext(const QString &instrName, const QString &attrName, const QString &attrValue) const Q_DECL_OVERRIDE - { - QString location = createContextString(instrName); - return QStringLiteral("%1 with %2=\"%3\"").arg(location, attrName, attrValue); + auto mainDoc = QScxmlCompilerPrivate::get(&compiler)->scxmlDocument(); + if (mainDoc == nullptr) { + Q_ASSERT(!compiler.errors().isEmpty()); + const auto errors = compiler.errors(); + for (const QScxmlError &error : errors) + qWarning() << error.toString(); + return Q_NULLPTR; } -private: - DynamicStateMachine *m_stateMachine; - QVector<QAbstractState *> m_parents; - QHash<QAbstractTransition *, DocumentModel::Transition*> m_allTransitions; - QHash<DocumentModel::AbstractState *, QAbstractState *> m_docStatesToQStates; - QAbstractTransition *m_currentTransition; - QVector<QPair<QState *, DocumentModel::AbstractState *>> m_initialStates; - bool m_bindLate; - bool m_qtMode; - QVector<DocumentModel::DataElement *> m_dataElements; - QSet<QString> m_eventSignals; - QSet<QString> m_eventSlots; - QHash<QString, QAbstractState *> m_stateNames; - QSet<QString> m_subStateMachineNames; -}; - -inline QScxmlInvokableService *InvokeDynamicScxmlFactory::invoke(QScxmlStateMachine *parent) -{ - auto child = QStateMachineBuilder().build(m_content.data()); + auto childStateMachine = DynamicStateMachine::build(mainDoc); - auto dm = QScxmlDataModelPrivate::instantiateDataModel(m_content->root->dataModel); - dm->setParent(child); - child->setDataModel(dm); + auto dm = QScxmlDataModelPrivate::instantiateDataModel(mainDoc->root->dataModel); + dm->setParent(childStateMachine); + childStateMachine->setDataModel(dm); - return finishInvoke(child, parent); + return invokeStatic(childStateMachine, parentStateMachine); } #endif // BUILD_QSCXMLC -} // anonymous namespace - /*! - * \class QScxmlParser - * \brief The QScxmlParser class is a parser for SCXML files. + * \class QScxmlCompiler + * \brief The QScxmlCompiler class is a compiler for SCXML files. * \since 5.7 * \inmodule QtScxml * - * Parses an \l{SCXML Specification}{SCXML} file. It can also dynamically instantiate a - * state machine for a successfully parsed SCXML file. If parsing failed and - * instantiateStateMachine() is called, the new state machine cannot start. All errors are - * returned by QScxmlStateMachine::parseErrors(). + * Parses an \l{SCXML Specification}{SCXML} file and dynamically instantiates a + * state machine for a successfully parsed SCXML file. If parsing fails, the + * new state machine cannot start. All errors are returned by + * QScxmlStateMachine::parseErrors(). * * To load an SCXML file, QScxmlStateMachine::fromFile or QScxmlStateMachine::fromData should be - * used. Using QScxmlParser directly is only needed when the parser needs to use a custom - * QScxmlParser::Loader. - */ - -/*! - \enum QScxmlParser::QtMode - - This enum specifies if the document should be parsed in Qt mode. In Qt - mode, event and state names have to be valid C++ identifiers. If that is - the case some additional convenience methods are generated. If not, the - parser will reject the document. Qt mode can be enabled in the document - itself by adding an XML comment of the form: - - \c {<!-- enable-qt-mode: yes -->} - - \value QtModeDisabled - Ignore the XML comment and do not generate additional methods. - \value QtModeEnabled - Force parsing in Qt mode and try to generate the additional methods, - no matter if the XML comment is present. - \value QtModeFromInputFile - Enable Qt mode only if the XML comment is present in the document. + * used. Using QScxmlCompiler directly is only needed when the compiler needs to use a custom + * QScxmlCompiler::Loader. */ /*! - * Creates a new SCXML parser for the specified \a reader. + * Creates a new SCXML compiler for the specified \a reader. */ -QScxmlParser::QScxmlParser(QXmlStreamReader *reader) - : d(new QScxmlParserPrivate(this, reader)) +QScxmlCompiler::QScxmlCompiler(QXmlStreamReader *reader) + : d(new QScxmlCompilerPrivate(reader)) { } /*! - * Destroys the SCXML parser. + * Destroys the SCXML compiler. */ -QScxmlParser::~QScxmlParser() +QScxmlCompiler::~QScxmlCompiler() { delete d; } @@ -1431,7 +748,7 @@ QScxmlParser::~QScxmlParser() * * \sa setFileName() */ -QString QScxmlParser::fileName() const +QString QScxmlCompiler::fileName() const { return d->fileName(); } @@ -1443,82 +760,98 @@ QString QScxmlParser::fileName() const * * \sa fileName() */ -void QScxmlParser::setFileName(const QString &fileName) +void QScxmlCompiler::setFileName(const QString &fileName) { d->setFileName(fileName); } /*! - * Returns the loader that is currently used to resolve and load URIs. + * Returns the loader that is currently used to resolve and load URIs for the + * SCXML compiler. * * \sa setLoader() */ -QScxmlParser::Loader *QScxmlParser::loader() const +QScxmlCompiler::Loader *QScxmlCompiler::loader() const { return d->loader(); } /*! - * Sets \a newLoader to be used for resolving and loading URIs. + * Sets \a newLoader to be used for resolving and loading URIs for the SCXML + * compiler. * * \sa loader() */ -void QScxmlParser::setLoader(QScxmlParser::Loader *newLoader) +void QScxmlCompiler::setLoader(QScxmlCompiler::Loader *newLoader) { d->setLoader(newLoader); } /*! - * Parses an SCXML file. + * Parses an SCXML file and creates a new state machine from it. + * + * If parsing is successful, the returned state machine can be initialized and started. If + * parsing fails, QScxmlStateMachine::parseErrors() can be used to retrieve a list of errors. */ -void QScxmlParser::parse() +QScxmlStateMachine *QScxmlCompiler::compile() { d->readDocument(); - d->verifyDocument(); + if (d->errors().isEmpty()) { + // Only verify the document if there were no parse errors: if there were any, the document + // is incomplete and will contain errors for sure. There is no need to heap more errors on + // top of other errors. + d->verifyDocument(); + } + return d->instantiateStateMachine(); } /*! + * \internal * Instantiates a new state machine from the parsed SCXML. * * If parsing is successful, the returned state machine can be initialized and started. If * parsing fails, QScxmlStateMachine::parseErrors() can be used to retrieve a list of errors. * * \note The instantiated state machine will not have an associated data model set. - * \sa QScxmlParser::instantiateDataModel + * \sa QScxmlCompilerPrivate::instantiateDataModel */ -QScxmlStateMachine *QScxmlParser::instantiateStateMachine() const +QScxmlStateMachine *QScxmlCompilerPrivate::instantiateStateMachine() const { #ifdef BUILD_QSCXMLC return Q_NULLPTR; #else // BUILD_QSCXMLC - DocumentModel::ScxmlDocument *doc = d->scxmlDocument(); + DocumentModel::ScxmlDocument *doc = scxmlDocument(); if (doc && doc->root) { - return QStateMachineBuilder().build(doc); + auto stateMachine = DynamicStateMachine::build(doc); + instantiateDataModel(stateMachine); + return stateMachine; } else { class InvalidStateMachine: public QScxmlStateMachine { public: - InvalidStateMachine() + InvalidStateMachine() : QScxmlStateMachine(&QScxmlStateMachine::staticMetaObject) {} }; auto stateMachine = new InvalidStateMachine; QScxmlStateMachinePrivate::get(stateMachine)->parserData()->m_errors = errors(); + instantiateDataModel(stateMachine); return stateMachine; } #endif // BUILD_QSCXMLC } /*! + * \internal * Instantiates the data model as described in the SCXML file. * * After instantiation, the \a stateMachine takes ownership of the data model. */ -void QScxmlParser::instantiateDataModel(QScxmlStateMachine *stateMachine) const +void QScxmlCompilerPrivate::instantiateDataModel(QScxmlStateMachine *stateMachine) const { #ifdef BUILD_QSCXMLC Q_UNUSED(stateMachine) #else - auto doc = d->scxmlDocument(); + auto doc = scxmlDocument(); auto root = doc ? doc->root : Q_NULLPTR; if (root == Q_NULLPTR) { qWarning() << "SCXML document has no root element"; @@ -1535,48 +868,12 @@ void QScxmlParser::instantiateDataModel(QScxmlStateMachine *stateMachine) const /*! * Returns the list of parse errors. */ -QVector<QScxmlError> QScxmlParser::errors() const +QVector<QScxmlError> QScxmlCompiler::errors() const { return d->errors(); } -/*! - * Adds the error message \a msg. - * - * The line and column numbers for the error message are the current line and - * column numbers of the QXmlStreamReader. - */ -void QScxmlParser::addError(const QString &msg) -{ - d->addError(msg); -} - -/*! - * Returns how the parser decides if the SCXML document should conform to Qt - * mode. - * - * \sa QtMode - */ -QScxmlParser::QtMode QScxmlParser::qtMode() const -{ - return d->qtMode(); -} - -/*! - * Sets the \c qtMode to \a mode. This property overrides the XML comment. You - * can force Qt mode to be used by setting it to \c QtModeEnabled or force any - * XML comments to be ignored and Qt mode to be used by setting it to - * \c QtModeDisabled. The default is \c QtModeFromInputFile, which will switch - * Qt mode on if the XML comment is present in the source file. - * - * \sa QtMode - */ -void QScxmlParser::setQtMode(QScxmlParser::QtMode mode) -{ - d->setQtMode(mode); -} - -bool QScxmlParserPrivate::ParserState::collectChars() { +bool QScxmlCompilerPrivate::ParserState::collectChars() { switch (kind) { case Content: case Data: @@ -1588,11 +885,11 @@ bool QScxmlParserPrivate::ParserState::collectChars() { return false; } -bool QScxmlParserPrivate::ParserState::validChild(ParserState::Kind child) const { +bool QScxmlCompilerPrivate::ParserState::validChild(ParserState::Kind child) const { return validChild(kind, child); } -bool QScxmlParserPrivate::ParserState::validChild(ParserState::Kind parent, ParserState::Kind child) +bool QScxmlCompilerPrivate::ParserState::validChild(ParserState::Kind parent, ParserState::Kind child) { switch (parent) { case ParserState::Scxml: @@ -1697,7 +994,7 @@ bool QScxmlParserPrivate::ParserState::validChild(ParserState::Kind parent, Pars return false; } -bool QScxmlParserPrivate::ParserState::isExecutableContent(ParserState::Kind kind) { +bool QScxmlCompilerPrivate::ParserState::isExecutableContent(ParserState::Kind kind) { switch (kind) { case Raise: case Send: @@ -1715,7 +1012,7 @@ bool QScxmlParserPrivate::ParserState::isExecutableContent(ParserState::Kind kin return false; } -QScxmlParserPrivate::ParserState::Kind QScxmlParserPrivate::ParserState::nameToParserStateKind(const QStringRef &name) +QScxmlCompilerPrivate::ParserState::Kind QScxmlCompilerPrivate::ParserState::nameToParserStateKind(const QStringRef &name) { static QMap<QString, ParserState::Kind> nameToKind; if (nameToKind.isEmpty()) { @@ -1756,7 +1053,7 @@ QScxmlParserPrivate::ParserState::Kind QScxmlParserPrivate::ParserState::nameToP return None; } -QStringList QScxmlParserPrivate::ParserState::requiredAttributes(QScxmlParserPrivate::ParserState::Kind kind) +QStringList QScxmlCompilerPrivate::ParserState::requiredAttributes(QScxmlCompilerPrivate::ParserState::Kind kind) { switch (kind) { case Scxml: return QStringList() << QStringLiteral("version"); @@ -1791,7 +1088,7 @@ QStringList QScxmlParserPrivate::ParserState::requiredAttributes(QScxmlParserPri return QStringList(); } -QStringList QScxmlParserPrivate::ParserState::optionalAttributes(QScxmlParserPrivate::ParserState::Kind kind) +QStringList QScxmlCompilerPrivate::ParserState::optionalAttributes(QScxmlCompilerPrivate::ParserState::Kind kind) { switch (kind) { case Scxml: return QStringList() << QStringLiteral("initial") @@ -1858,6 +1155,15 @@ DocumentModel::Node::~Node() { } +DocumentModel::AbstractState *DocumentModel::Node::asAbstractState() +{ + if (State *state = asState()) + return state; + if (HistoryState *history = asHistoryState()) + return history; + return Q_NULLPTR; +} + void DocumentModel::DataElement::accept(DocumentModel::NodeVisitor *visitor) { visitor->visit(this); @@ -1871,7 +1177,7 @@ void DocumentModel::Param::accept(DocumentModel::NodeVisitor *visitor) void DocumentModel::DoneData::accept(DocumentModel::NodeVisitor *visitor) { if (visitor->visit(this)) { - foreach (Param *param, params) + for (Param *param : qAsConst(params)) param->accept(visitor); } visitor->endVisit(this); @@ -1944,7 +1250,7 @@ void DocumentModel::State::accept(DocumentModel::NodeVisitor *visitor) visitor->visit(onExit); if (doneData) doneData->accept(visitor); - foreach (Invoke *invoke, invokes) + for (Invoke *invoke : qAsConst(invokes)) invoke->accept(visitor); } visitor->endVisit(this); @@ -1982,69 +1288,47 @@ void DocumentModel::Scxml::accept(DocumentModel::NodeVisitor *visitor) DocumentModel::NodeVisitor::~NodeVisitor() {} -bool DocumentModel::isValidQPropertyName(const QString &str) -{ - return !str.isEmpty() && !str.contains(QLatin1Char('(')) && !str.contains(QLatin1Char(')')); -} - -bool DocumentModel::isEventToBeGenerated(const QString &event) -{ - return !event.contains(QLatin1Char('.')); -} - /*! - * \class QScxmlParser::Loader - * \brief The Loader class is a URI resolver and resource loader for an SCXML parser. - * \since 5.7 + * \class QScxmlCompiler::Loader + * \brief The Loader class is a URI resolver and resource loader for an SCXML compiler. + * \since 5.8 * \inmodule QtScxml */ /*! - * Creates a new loader for the specified \a parser. + * Creates a new loader. */ -QScxmlParser::Loader::Loader(QScxmlParser *parser) - : m_parser(parser) +QScxmlCompiler::Loader::Loader() { - Q_ASSERT(parser); } /*! * Destroys the loader. */ -QScxmlParser::Loader::~Loader() +QScxmlCompiler::Loader::~Loader() {} /*! - * Returns the parser that is associated with this loader. - */ -QScxmlParser *QScxmlParser::Loader::parser() const -{ - return m_parser; -} - -/*! - * \fn QScxmlParser::Loader::load(const QString &name, const QString &baseDir, bool *ok) + * \fn QScxmlCompiler::Loader::load(const QString &name, const QString &baseDir, QStringList *errors) * Resolves the URI \a name and loads an SCXML file from the directory - * specified by \a baseDir. The boolean parameter \a ok indicates whether - * the loading was successful. + * specified by \a baseDir. \a errors contains information about the errors that + * might have occurred. * * Returns a QByteArray that stores the contents of the file. */ -QScxmlParserPrivate *QScxmlParserPrivate::get(QScxmlParser *parser) +QScxmlCompilerPrivate *QScxmlCompilerPrivate::get(QScxmlCompiler *compiler) { - return parser->d; + return compiler->d; } -QScxmlParserPrivate::QScxmlParserPrivate(QScxmlParser *parser, QXmlStreamReader *reader) +QScxmlCompilerPrivate::QScxmlCompilerPrivate(QXmlStreamReader *reader) : m_currentState(Q_NULLPTR) - , m_defaultLoader(parser) , m_loader(&m_defaultLoader) , m_reader(reader) - , m_qtMode(QScxmlParser::QtModeFromInputFile) {} -bool QScxmlParserPrivate::verifyDocument() +bool QScxmlCompilerPrivate::verifyDocument() { if (!m_doc) return false; @@ -2059,65 +1343,60 @@ bool QScxmlParserPrivate::verifyDocument() return false; } -DocumentModel::ScxmlDocument *QScxmlParserPrivate::scxmlDocument() const +DocumentModel::ScxmlDocument *QScxmlCompilerPrivate::scxmlDocument() const { return m_doc && m_errors.isEmpty() ? m_doc.data() : Q_NULLPTR; } -QString QScxmlParserPrivate::fileName() const +QString QScxmlCompilerPrivate::fileName() const { return m_fileName; } -void QScxmlParserPrivate::setFileName(const QString &fileName) +void QScxmlCompilerPrivate::setFileName(const QString &fileName) { m_fileName = fileName; } -QScxmlParser::Loader *QScxmlParserPrivate::loader() const +QScxmlCompiler::Loader *QScxmlCompilerPrivate::loader() const { return m_loader; } -void QScxmlParserPrivate::setLoader(QScxmlParser::Loader *loader) +void QScxmlCompilerPrivate::setLoader(QScxmlCompiler::Loader *loader) { m_loader = loader; } -void QScxmlParserPrivate::parseSubDocument(DocumentModel::Invoke *parentInvoke, QXmlStreamReader *reader, const QString &fileName)\ +void QScxmlCompilerPrivate::parseSubDocument(DocumentModel::Invoke *parentInvoke, + QXmlStreamReader *reader, + const QString &fileName) { - QScxmlParser p(reader); + QScxmlCompiler p(reader); p.setFileName(fileName); + p.setLoader(loader()); p.d->readDocument(); parentInvoke->content.reset(p.d->m_doc.take()); m_doc->allSubDocuments.append(parentInvoke->content.data()); m_errors.append(p.errors()); } -bool QScxmlParserPrivate::parseSubElement(DocumentModel::Invoke *parentInvoke, QXmlStreamReader *reader, const QString &fileName) +bool QScxmlCompilerPrivate::parseSubElement(DocumentModel::Invoke *parentInvoke, + QXmlStreamReader *reader, + const QString &fileName) { - QScxmlParser p(reader); + QScxmlCompiler p(reader); p.setFileName(fileName); + p.setLoader(loader()); p.d->resetDocument(); bool ok = p.d->readElement(); parentInvoke->content.reset(p.d->m_doc.take()); m_doc->allSubDocuments.append(parentInvoke->content.data()); m_errors.append(p.errors()); - parentInvoke->content->qtMode = m_doc->qtMode; return ok; } -static bool isWordEnd(const QStringRef &str, int start) -{ - if (str.size() <= start) { - return true; - } - - QChar ch = str.at(start); - return ch.isSpace(); -} - -bool QScxmlParserPrivate::preReadElementScxml() +bool QScxmlCompilerPrivate::preReadElementScxml() { if (m_doc->root) { addError(QLatin1String("Doc root already allocated")); @@ -2176,7 +1455,7 @@ bool QScxmlParserPrivate::preReadElementScxml() } -bool QScxmlParserPrivate::preReadElementState() +bool QScxmlCompilerPrivate::preReadElementState() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto newState = m_doc->newState(m_currentState, DocumentModel::State::Normal, xmlLocation()); @@ -2191,7 +1470,7 @@ bool QScxmlParserPrivate::preReadElementState() return true; } -bool QScxmlParserPrivate::preReadElementParallel() +bool QScxmlCompilerPrivate::preReadElementParallel() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto newState = m_doc->newState(m_currentState, DocumentModel::State::Parallel, xmlLocation()); @@ -2202,7 +1481,7 @@ bool QScxmlParserPrivate::preReadElementParallel() return true; } -bool QScxmlParserPrivate::preReadElementInitial() +bool QScxmlCompilerPrivate::preReadElementInitial() { DocumentModel::AbstractState *parent = currentParent(); if (!parent) { @@ -2220,15 +1499,36 @@ bool QScxmlParserPrivate::preReadElementInitial() addError(QStringLiteral("Explicit initial state for parallel states not supported (only implicitly through the initial states of its substates)")); return false; } - auto newState = m_doc->newState(m_currentState, DocumentModel::State::Initial, xmlLocation()); - m_currentState = newState; return true; } -bool QScxmlParserPrivate::preReadElementTransition() -{ +bool QScxmlCompilerPrivate::preReadElementTransition() +{ + // Parser stack at this point: + // <transition> + // <initial> + // <state> or <scxml> + // + // Or: + // <transition> + // <state> or <scxml> + + DocumentModel::Transition *transition = nullptr; + if (previous().kind == ParserState::Initial) { + transition = m_doc->newTransition(nullptr, xmlLocation()); + const auto &initialParentState = m_stack.at(m_stack.size() - 3); + if (initialParentState.kind == ParserState::Scxml) { + m_currentState->asScxml()->initialTransition = transition; + } else if (initialParentState.kind == ParserState::State) { + m_currentState->asState()->initialTransition = transition; + } else { + Q_UNREACHABLE(); + } + } else { + transition = m_doc->newTransition(m_currentState, xmlLocation()); + } + const QXmlStreamAttributes attributes = m_reader->attributes(); - auto transition = m_doc->newTransition(m_currentState, xmlLocation()); transition->events = attributes.value(QLatin1String("event")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); transition->targets = attributes.value(QLatin1String("target")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); if (attributes.hasAttribute(QStringLiteral("cond"))) @@ -2246,7 +1546,7 @@ bool QScxmlParserPrivate::preReadElementTransition() return true; } -bool QScxmlParserPrivate::preReadElementFinal() +bool QScxmlCompilerPrivate::preReadElementFinal() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto newState = m_doc->newState(m_currentState, DocumentModel::State::Final, xmlLocation()); @@ -2256,7 +1556,7 @@ bool QScxmlParserPrivate::preReadElementFinal() return true; } -bool QScxmlParserPrivate::preReadElementHistory() +bool QScxmlCompilerPrivate::preReadElementHistory() { const QXmlStreamAttributes attributes = m_reader->attributes(); @@ -2282,7 +1582,7 @@ bool QScxmlParserPrivate::preReadElementHistory() return true; } -bool QScxmlParserPrivate::preReadElementOnEntry() +bool QScxmlCompilerPrivate::preReadElementOnEntry() { const ParserState::Kind previousKind = previous().kind; switch (previousKind) { @@ -2301,7 +1601,7 @@ bool QScxmlParserPrivate::preReadElementOnEntry() return true; } -bool QScxmlParserPrivate::preReadElementOnExit() +bool QScxmlCompilerPrivate::preReadElementOnExit() { ParserState::Kind previousKind = previous().kind; switch (previousKind) { @@ -2320,7 +1620,7 @@ bool QScxmlParserPrivate::preReadElementOnExit() return true; } -bool QScxmlParserPrivate::preReadElementRaise() +bool QScxmlCompilerPrivate::preReadElementRaise() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto raise = m_doc->newNode<DocumentModel::Raise>(xmlLocation()); @@ -2329,7 +1629,7 @@ bool QScxmlParserPrivate::preReadElementRaise() return true; } -bool QScxmlParserPrivate::preReadElementIf() +bool QScxmlCompilerPrivate::preReadElementIf() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto *ifI = m_doc->newNode<DocumentModel::If>(xmlLocation()); @@ -2339,7 +1639,7 @@ bool QScxmlParserPrivate::preReadElementIf() return true; } -bool QScxmlParserPrivate::preReadElementElseIf() +bool QScxmlCompilerPrivate::preReadElementElseIf() { const QXmlStreamAttributes attributes = m_reader->attributes(); @@ -2352,7 +1652,7 @@ bool QScxmlParserPrivate::preReadElementElseIf() return true; } -bool QScxmlParserPrivate::preReadElementElse() +bool QScxmlCompilerPrivate::preReadElementElse() { DocumentModel::If *ifI = lastIf(); if (!ifI) @@ -2362,7 +1662,7 @@ bool QScxmlParserPrivate::preReadElementElse() return true; } -bool QScxmlParserPrivate::preReadElementForeach() +bool QScxmlCompilerPrivate::preReadElementForeach() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto foreachI = m_doc->newNode<DocumentModel::Foreach>(xmlLocation()); @@ -2374,7 +1674,7 @@ bool QScxmlParserPrivate::preReadElementForeach() return true; } -bool QScxmlParserPrivate::preReadElementLog() +bool QScxmlCompilerPrivate::preReadElementLog() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto logI = m_doc->newNode<DocumentModel::Log>(xmlLocation()); @@ -2384,12 +1684,12 @@ bool QScxmlParserPrivate::preReadElementLog() return true; } -bool QScxmlParserPrivate::preReadElementDataModel() +bool QScxmlCompilerPrivate::preReadElementDataModel() { return true; } -bool QScxmlParserPrivate::preReadElementData() +bool QScxmlCompilerPrivate::preReadElementData() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto data = m_doc->newNode<DocumentModel::DataElement>(xmlLocation()); @@ -2406,7 +1706,7 @@ bool QScxmlParserPrivate::preReadElementData() return true; } -bool QScxmlParserPrivate::preReadElementAssign() +bool QScxmlCompilerPrivate::preReadElementAssign() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto assign = m_doc->newNode<DocumentModel::Assign>(xmlLocation()); @@ -2416,7 +1716,7 @@ bool QScxmlParserPrivate::preReadElementAssign() return true; } -bool QScxmlParserPrivate::preReadElementDoneData() +bool QScxmlCompilerPrivate::preReadElementDoneData() { DocumentModel::State *s = m_currentState->asState(); if (s && s->type == DocumentModel::State::Final) { @@ -2431,7 +1731,7 @@ bool QScxmlParserPrivate::preReadElementDoneData() return true; } -bool QScxmlParserPrivate::preReadElementContent() +bool QScxmlCompilerPrivate::preReadElementContent() { const QXmlStreamAttributes attributes = m_reader->attributes(); ParserState::Kind previousKind = previous().kind; @@ -2444,7 +1744,7 @@ bool QScxmlParserPrivate::preReadElementContent() case ParserState::Send: { DocumentModel::Send *s = previous().instruction->asSend(); Q_ASSERT(s); - s->content = attributes.value(QLatin1String("expr")).toString(); + s->contentexpr = attributes.value(QLatin1String("expr")).toString(); } break; case ParserState::Invoke: { DocumentModel::Invoke *i = previous().instruction->asInvoke(); @@ -2461,7 +1761,7 @@ bool QScxmlParserPrivate::preReadElementContent() return true; } -bool QScxmlParserPrivate::preReadElementParam() +bool QScxmlCompilerPrivate::preReadElementParam() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto param = m_doc->newNode<DocumentModel::Param>(xmlLocation()); @@ -2493,7 +1793,7 @@ bool QScxmlParserPrivate::preReadElementParam() return true; } -bool QScxmlParserPrivate::preReadElementScript() +bool QScxmlCompilerPrivate::preReadElementScript() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto *script = m_doc->newNode<DocumentModel::Script>(xmlLocation()); @@ -2502,7 +1802,7 @@ bool QScxmlParserPrivate::preReadElementScript() return true; } -bool QScxmlParserPrivate::preReadElementSend() +bool QScxmlCompilerPrivate::preReadElementSend() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto *send = m_doc->newNode<DocumentModel::Send>(xmlLocation()); @@ -2522,7 +1822,7 @@ bool QScxmlParserPrivate::preReadElementSend() return true; } -bool QScxmlParserPrivate::preReadElementCancel() +bool QScxmlCompilerPrivate::preReadElementCancel() { const QXmlStreamAttributes attributes = m_reader->attributes(); auto *cancel = m_doc->newNode<DocumentModel::Cancel>(xmlLocation()); @@ -2532,7 +1832,7 @@ bool QScxmlParserPrivate::preReadElementCancel() return true; } -bool QScxmlParserPrivate::preReadElementInvoke() +bool QScxmlCompilerPrivate::preReadElementInvoke() { const QXmlStreamAttributes attributes = m_reader->attributes(); DocumentModel::State *parentState = m_currentState->asState(); @@ -2563,7 +1863,7 @@ bool QScxmlParserPrivate::preReadElementInvoke() return true; } -bool QScxmlParserPrivate::preReadElementFinalize() +bool QScxmlCompilerPrivate::preReadElementFinalize() { auto instr = previous().instruction; if (!instr) { @@ -2579,98 +1879,97 @@ bool QScxmlParserPrivate::preReadElementFinalize() return true; } -bool QScxmlParserPrivate::postReadElementScxml() +bool QScxmlCompilerPrivate::postReadElementScxml() { return true; } -bool QScxmlParserPrivate::postReadElementState() +bool QScxmlCompilerPrivate::postReadElementState() { currentStateUp(); return true; } -bool QScxmlParserPrivate::postReadElementParallel() +bool QScxmlCompilerPrivate::postReadElementParallel() { currentStateUp(); return true; } -bool QScxmlParserPrivate::postReadElementInitial() +bool QScxmlCompilerPrivate::postReadElementInitial() { - currentStateUp(); return true; } -bool QScxmlParserPrivate::postReadElementTransition() +bool QScxmlCompilerPrivate::postReadElementTransition() { return true; } -bool QScxmlParserPrivate::postReadElementFinal() +bool QScxmlCompilerPrivate::postReadElementFinal() { currentStateUp(); return true; } -bool QScxmlParserPrivate::postReadElementHistory() +bool QScxmlCompilerPrivate::postReadElementHistory() { currentStateUp(); return true; } -bool QScxmlParserPrivate::postReadElementOnEntry() +bool QScxmlCompilerPrivate::postReadElementOnEntry() { return true; } -bool QScxmlParserPrivate::postReadElementOnExit() +bool QScxmlCompilerPrivate::postReadElementOnExit() { return true; } -bool QScxmlParserPrivate::postReadElementRaise() +bool QScxmlCompilerPrivate::postReadElementRaise() { return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementIf() +bool QScxmlCompilerPrivate::postReadElementIf() { return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementElseIf() +bool QScxmlCompilerPrivate::postReadElementElseIf() { return true; } -bool QScxmlParserPrivate::postReadElementElse() +bool QScxmlCompilerPrivate::postReadElementElse() { return true; } -bool QScxmlParserPrivate::postReadElementForeach() +bool QScxmlCompilerPrivate::postReadElementForeach() { return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementLog() +bool QScxmlCompilerPrivate::postReadElementLog() { return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementDataModel() +bool QScxmlCompilerPrivate::postReadElementDataModel() { return true; } -bool QScxmlParserPrivate::postReadElementData() +bool QScxmlCompilerPrivate::postReadElementData() { const ParserState parserState = current(); DocumentModel::DataElement *data = Q_NULLPTR; if (auto state = m_currentState->asState()) { data = state->dataElements.last(); - } else if (auto scxml = m_currentState->asNode()->asScxml()) { + } else if (auto scxml = m_currentState->asScxml()) { data = scxml->dataElements.last(); } else { Q_UNREACHABLE(); @@ -2711,17 +2010,17 @@ bool QScxmlParserPrivate::postReadElementData() return true; } -bool QScxmlParserPrivate::postReadElementAssign() +bool QScxmlCompilerPrivate::postReadElementAssign() { return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementDoneData() +bool QScxmlCompilerPrivate::postReadElementDoneData() { return true; } -bool QScxmlParserPrivate::postReadElementContent() +bool QScxmlCompilerPrivate::postReadElementContent() { const ParserState parserState = current(); if (!parserState.chars.trimmed().isEmpty()) { @@ -2740,12 +2039,12 @@ bool QScxmlParserPrivate::postReadElementContent() return true; } -bool QScxmlParserPrivate::postReadElementParam() +bool QScxmlCompilerPrivate::postReadElementParam() { return true; } -bool QScxmlParserPrivate::postReadElementScript() +bool QScxmlCompilerPrivate::postReadElementScript() { const ParserState parserState = current(); DocumentModel::Script *scriptI = parserState.instruction->asScript(); @@ -2769,17 +2068,17 @@ bool QScxmlParserPrivate::postReadElementScript() return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementSend() +bool QScxmlCompilerPrivate::postReadElementSend() { return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementCancel() +bool QScxmlCompilerPrivate::postReadElementCancel() { return flushInstruction(); } -bool QScxmlParserPrivate::postReadElementInvoke() +bool QScxmlCompilerPrivate::postReadElementInvoke() { DocumentModel::Invoke *i = current().instruction->asInvoke(); const QString fileName = i->src; @@ -2793,11 +2092,6 @@ bool QScxmlParserPrivate::postReadElementInvoke() QXmlStreamReader reader(data); parseSubDocument(i, &reader, fileName); } - } else { // invoke always expects sub document - DocumentModel::ScxmlDocument *doc = new DocumentModel::ScxmlDocument(m_fileName); - doc->root = new DocumentModel::Scxml(DocumentModel::XmlLocation(0, 0)); - i->content.reset(doc); - m_doc->allSubDocuments.append(i->content.data()); } } else if (!fileName.isEmpty()) { addError(QStringLiteral("both src and content given to invoke")); @@ -2806,23 +2100,19 @@ bool QScxmlParserPrivate::postReadElementInvoke() return true; } -bool QScxmlParserPrivate::postReadElementFinalize() +bool QScxmlCompilerPrivate::postReadElementFinalize() { return true; } -void QScxmlParserPrivate::resetDocument() +void QScxmlCompilerPrivate::resetDocument() { m_doc.reset(new DocumentModel::ScxmlDocument(fileName())); } -bool QScxmlParserPrivate::readDocument() +bool QScxmlCompilerPrivate::readDocument() { resetDocument(); - if (m_qtMode == QScxmlParser::QtModeEnabled) - m_doc->qtMode = true; - else if (m_qtMode == QScxmlParser::QtModeDisabled) - m_doc->qtMode = false; m_currentState = m_doc->root; for (bool finished = false; !finished && !m_reader->hasError();) { switch (m_reader->readNext()) { @@ -2849,9 +2139,6 @@ bool QScxmlParserPrivate::readDocument() case QXmlStreamReader::EndElement : finished = true; break; - case QXmlStreamReader::Comment: - parseComment(); - break; default : break; } @@ -2869,7 +2156,7 @@ bool QScxmlParserPrivate::readDocument() return true; } -bool QScxmlParserPrivate::readElement() +bool QScxmlCompilerPrivate::readElement() { const QStringRef currentTag = m_reader->name(); const QXmlStreamAttributes attributes = m_reader->attributes(); @@ -2964,9 +2251,6 @@ bool QScxmlParserPrivate::readElement() if (current().collectChars()) current().chars.append(m_reader->text()); break; - case QXmlStreamReader::Comment: - parseComment(); - break; default : break; } @@ -3012,40 +2296,13 @@ bool QScxmlParserPrivate::readElement() return true; } -void QScxmlParserPrivate::parseComment() -{ - static const QString qtModeSwitch = QStringLiteral("enable-qt-mode:"); - const QStringRef commentText = m_reader->text(); - int qtModeIdx = commentText.indexOf(qtModeSwitch); - if (qtModeIdx != -1) { - qtModeIdx += qtModeSwitch.size(); - while (qtModeIdx < commentText.size()) { - if (commentText.at(qtModeIdx).isSpace()) { - ++qtModeIdx; - } else { - break; - } - } - const QStringRef value = commentText.mid(qtModeIdx); - if (value.startsWith(QStringLiteral("yes")) && isWordEnd(value, 3)) { - if (m_qtMode == QScxmlParser::QtModeFromInputFile) - m_doc->qtMode = true; - } else if (value.startsWith(QStringLiteral("no")) && isWordEnd(value, 2)) { - if (m_qtMode == QScxmlParser::QtModeFromInputFile) - m_doc->qtMode = false; - } else { - addError(QStringLiteral("expected 'yes' or 'no' after enable-qt-mode in comment")); - } - } -} - -void QScxmlParserPrivate::currentStateUp() +void QScxmlCompilerPrivate::currentStateUp() { Q_ASSERT(m_currentState->parent); m_currentState = m_currentState->parent; } -bool QScxmlParserPrivate::flushInstruction() +bool QScxmlCompilerPrivate::flushInstruction() { if (!hasPrevious()) { addError(QStringLiteral("missing instructionContainer")); @@ -3061,48 +2318,45 @@ bool QScxmlParserPrivate::flushInstruction() } -QByteArray QScxmlParserPrivate::load(const QString &name, bool *ok) const +QByteArray QScxmlCompilerPrivate::load(const QString &name, bool *ok) { - return m_loader->load(name, m_fileName.isEmpty() ? - QString() : QFileInfo(m_fileName).path(), ok); + QStringList errs; + const QByteArray result = m_loader->load(name, m_fileName.isEmpty() ? + QString() : QFileInfo(m_fileName).path(), &errs); + for (const QString &err : errs) + addError(err); + + *ok = errs.isEmpty(); + + return result; } -QVector<QScxmlError> QScxmlParserPrivate::errors() const +QVector<QScxmlError> QScxmlCompilerPrivate::errors() const { return m_errors; } -void QScxmlParserPrivate::addError(const QString &msg) +void QScxmlCompilerPrivate::addError(const QString &msg) { m_errors.append(QScxmlError(m_fileName, m_reader->lineNumber(), m_reader->columnNumber(), msg)); } -void QScxmlParserPrivate::addError(const DocumentModel::XmlLocation &location, const QString &msg) +void QScxmlCompilerPrivate::addError(const DocumentModel::XmlLocation &location, const QString &msg) { m_errors.append(QScxmlError(m_fileName, location.line, location.column, msg)); } -QScxmlParser::QtMode QScxmlParserPrivate::qtMode() const -{ - return m_qtMode; -} - -void QScxmlParserPrivate::setQtMode(QScxmlParser::QtMode mode) -{ - m_qtMode = mode; -} - -DocumentModel::AbstractState *QScxmlParserPrivate::currentParent() const +DocumentModel::AbstractState *QScxmlCompilerPrivate::currentParent() const { return m_currentState ? m_currentState->asAbstractState() : Q_NULLPTR; } -DocumentModel::XmlLocation QScxmlParserPrivate::xmlLocation() const +DocumentModel::XmlLocation QScxmlCompilerPrivate::xmlLocation() const { return DocumentModel::XmlLocation(m_reader->lineNumber(), m_reader->columnNumber()); } -bool QScxmlParserPrivate::maybeId(const QXmlStreamAttributes &attributes, QString *id) +bool QScxmlCompilerPrivate::maybeId(const QXmlStreamAttributes &attributes, QString *id) { Q_ASSERT(id); QString idStr = attributes.value(QLatin1String("id")).toString(); @@ -3117,7 +2371,7 @@ bool QScxmlParserPrivate::maybeId(const QXmlStreamAttributes &attributes, QStrin return true; } -DocumentModel::If *QScxmlParserPrivate::lastIf() +DocumentModel::If *QScxmlCompilerPrivate::lastIf() { if (!hasPrevious()) { addError(QStringLiteral("No previous instruction found for else block")); @@ -3137,35 +2391,35 @@ DocumentModel::If *QScxmlParserPrivate::lastIf() return ifI; } -QScxmlParserPrivate::ParserState &QScxmlParserPrivate::current() +QScxmlCompilerPrivate::ParserState &QScxmlCompilerPrivate::current() { return m_stack.last(); } -QScxmlParserPrivate::ParserState &QScxmlParserPrivate::previous() +QScxmlCompilerPrivate::ParserState &QScxmlCompilerPrivate::previous() { return m_stack[m_stack.count() - 2]; } -bool QScxmlParserPrivate::hasPrevious() const +bool QScxmlCompilerPrivate::hasPrevious() const { return m_stack.count() > 1; } -bool QScxmlParserPrivate::checkAttributes(const QXmlStreamAttributes &attributes, - QScxmlParserPrivate::ParserState::Kind kind) +bool QScxmlCompilerPrivate::checkAttributes(const QXmlStreamAttributes &attributes, + QScxmlCompilerPrivate::ParserState::Kind kind) { return checkAttributes(attributes, ParserState::requiredAttributes(kind), ParserState::optionalAttributes(kind)); } -bool QScxmlParserPrivate::checkAttributes(const QXmlStreamAttributes &attributes, +bool QScxmlCompilerPrivate::checkAttributes(const QXmlStreamAttributes &attributes, const QStringList &requiredNames, const QStringList &optionalNames) { QStringList required = requiredNames; - foreach (const QXmlStreamAttribute &attribute, attributes) { + for (const QXmlStreamAttribute &attribute : attributes) { const QStringRef ns = attribute.namespaceUri(); if (!ns.isEmpty() && ns != scxmlNamespace && ns != qtScxmlNamespace) continue; @@ -3184,15 +2438,14 @@ bool QScxmlParserPrivate::checkAttributes(const QXmlStreamAttributes &attributes return true; } -QScxmlParserPrivate::DefaultLoader::DefaultLoader(QScxmlParser *parser) - : Loader(parser) +QScxmlCompilerPrivate::DefaultLoader::DefaultLoader() + : Loader() {} -QByteArray QScxmlParserPrivate::DefaultLoader::load(const QString &name, const QString &baseDir, bool *ok) +QByteArray QScxmlCompilerPrivate::DefaultLoader::load(const QString &name, const QString &baseDir, QStringList *errors) { - Q_ASSERT(ok != nullptr); - - *ok = false; + QStringList errs; + QByteArray contents; #ifdef BUILD_QSCXMLC QString cleanName = name; if (name.startsWith(QStringLiteral("file:"))) @@ -3201,28 +2454,29 @@ QByteArray QScxmlParserPrivate::DefaultLoader::load(const QString &name, const Q #else const QUrl url(name); if (!url.isLocalFile() && !url.isRelative()) - parser()->addError(QStringLiteral("src attribute is not a local file (%1)").arg(name)); + errs << QStringLiteral("src attribute is not a local file (%1)").arg(name); QFileInfo fInfo = url.isLocalFile() ? url.toLocalFile() : name; #endif // BUILD_QSCXMLC if (fInfo.isRelative()) fInfo = QFileInfo(QDir(baseDir).filePath(fInfo.filePath())); if (!fInfo.exists()) { - parser()->addError(QStringLiteral("src attribute resolves to non existing file (%1)").arg(fInfo.filePath())); + errs << QStringLiteral("src attribute resolves to non existing file (%1)").arg(fInfo.filePath()); } else { QFile f(fInfo.filePath()); - if (f.open(QFile::ReadOnly)) { - *ok = true; - return f.readAll(); - } else { - parser()->addError(QStringLiteral("Failure opening file %1: %2") - .arg(fInfo.filePath(), f.errorString())); - } + if (f.open(QFile::ReadOnly)) + contents = f.readAll(); + else + errs << QStringLiteral("Failure opening file %1: %2") + .arg(fInfo.filePath(), f.errorString()); } - return QByteArray(); + if (errors) + *errors = errs; + + return contents; } -QScxmlParserPrivate::ParserState::ParserState(QScxmlParserPrivate::ParserState::Kind someKind) +QScxmlCompilerPrivate::ParserState::ParserState(QScxmlCompilerPrivate::ParserState::Kind someKind) : kind(someKind) , instruction(0) , instructionContainer(0) diff --git a/src/scxml/qscxmlparser.h b/src/scxml/qscxmlcompiler.h index 3ce749f..411dfde 100644 --- a/src/scxml/qscxmlparser.h +++ b/src/scxml/qscxmlcompiler.h @@ -37,45 +37,33 @@ ** ****************************************************************************/ -#ifndef SCXMLPARSER_H -#define SCXMLPARSER_H +#ifndef QSCXMLCOMPILER_H +#define QSCXMLCOMPILER_H #include <QtScxml/qscxmlerror.h> - -#include <QStringList> -#include <QString> +#include <QtCore/qstring.h> QT_BEGIN_NAMESPACE class QXmlStreamReader; class QScxmlStateMachine; -class QScxmlParserPrivate; -class Q_SCXML_EXPORT QScxmlParser +class QScxmlCompilerPrivate; +class Q_SCXML_EXPORT QScxmlCompiler { public: class Q_SCXML_EXPORT Loader { public: - Loader(QScxmlParser *parser); + Loader(); virtual ~Loader(); - virtual QByteArray load(const QString &name, const QString &baseDir, bool *ok) = 0; - - protected: - QScxmlParser *parser() const; - - private: - QScxmlParser *m_parser; - }; - - enum QtMode { - QtModeDisabled, - QtModeEnabled, - QtModeFromInputFile + virtual QByteArray load(const QString &name, + const QString &baseDir, + QStringList *errors) = 0; }; public: - QScxmlParser(QXmlStreamReader *xmlReader); - ~QScxmlParser(); + QScxmlCompiler(QXmlStreamReader *xmlReader); + ~QScxmlCompiler(); QString fileName() const; void setFileName(const QString &fileName); @@ -83,21 +71,14 @@ public: Loader *loader() const; void setLoader(Loader *newLoader); - void parse(); - QScxmlStateMachine *instantiateStateMachine() const; - void instantiateDataModel(QScxmlStateMachine *stateMachine) const; - + QScxmlStateMachine *compile(); QVector<QScxmlError> errors() const; - void addError(const QString &msg); - - QtMode qtMode() const; - void setQtMode(QtMode mode); private: - friend class QScxmlParserPrivate; - QScxmlParserPrivate *d; + friend class QScxmlCompilerPrivate; + QScxmlCompilerPrivate *d; }; QT_END_NAMESPACE -#endif // SCXMLPARSER_H +#endif // QSCXMLCOMPILER_H diff --git a/src/scxml/qscxmlparser_p.h b/src/scxml/qscxmlcompiler_p.h index 3103605..6909c2c 100644 --- a/src/scxml/qscxmlparser_p.h +++ b/src/scxml/qscxmlcompiler_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef SCXMLPARSER_P_H -#define SCXMLPARSER_P_H +#ifndef QSCXMLCOMPILER_P_H +#define QSCXMLCOMPILER_P_H // // W A R N I N G @@ -51,7 +51,7 @@ // We mean it. // -#include "qscxmlparser.h" +#include "qscxmlcompiler.h" #include <QDir> #include <QFileInfo> @@ -98,6 +98,7 @@ struct Node { virtual Transition *asTransition() { return Q_NULLPTR; } virtual HistoryState *asHistoryState() { return Q_NULLPTR; } virtual Scxml *asScxml() { return Q_NULLPTR; } + AbstractState *asAbstractState(); private: Q_DISABLE_COPY(Node) @@ -158,6 +159,7 @@ struct Send: public Instruction QStringList namelist; QVector<Param *> params; QString content; + QString contentexpr; Send(const XmlLocation &xmlLocation): Instruction(xmlLocation) {} Send *asSend() Q_DECL_OVERRIDE { return this; } @@ -269,7 +271,6 @@ struct StateContainer virtual AbstractState *asAbstractState() { return Q_NULLPTR; } virtual State *asState() { return Q_NULLPTR; } virtual Scxml *asScxml() { return Q_NULLPTR; } - Node *asNode() { return dynamic_cast<Node *>(this); } }; struct AbstractState: public StateContainer @@ -281,7 +282,7 @@ struct AbstractState: public StateContainer struct State: public AbstractState, public StateOrTransition { - enum Type { Normal, Parallel, Initial, Final }; + enum Type { Normal, Parallel, Final }; QStringList initial; QVector<DataElement *> dataElements; @@ -292,12 +293,13 @@ struct State: public AbstractState, public StateOrTransition QVector<Invoke *> invokes; Type type; - QVector<AbstractState *> initialStates; // filled during verification + Transition *initialTransition; // when not set, it is filled during verification State(const XmlLocation &xmlLocation) : StateOrTransition(xmlLocation) , doneData(Q_NULLPTR) , type(Normal) + , initialTransition(Q_NULLPTR) {} void add(StateOrTransition *s) Q_DECL_OVERRIDE @@ -313,14 +315,14 @@ struct State: public AbstractState, public StateOrTransition struct Transition: public StateOrTransition { - enum Type { External, Internal }; + enum Type { Internal, External, Synthetic }; QStringList events; QScopedPointer<QString> condition; QStringList targets; InstructionSequence instructionsOnTransition; Type type; - QVector<AbstractState *> targetStates; // filled during verification + QVector<AbstractState *> targetStates; // when not set, it is filled during verification Transition(const XmlLocation &xmlLocation) : StateOrTransition(xmlLocation) @@ -379,7 +381,7 @@ struct Scxml: public StateContainer, public Node QScopedPointer<Script> script; InstructionSequence initialSetup; - QVector<AbstractState *> initialStates; // filled during verification + Transition *initialTransition; Scxml(const XmlLocation &xmlLocation) : Node(xmlLocation) @@ -407,13 +409,11 @@ struct ScxmlDocument QVector<Node *> allNodes; QVector<InstructionSequence *> allSequences; QVector<ScxmlDocument *> allSubDocuments; // weak pointers - bool qtMode; bool isVerified; ScxmlDocument(const QString &fileName) : fileName(fileName) , root(Q_NULLPTR) - , qtMode(false) , isVerified(false) {} @@ -449,7 +449,9 @@ struct ScxmlDocument { Transition *t = newNode<Transition>(xmlLocation); allTransitions.append(t); - parent->add(t); + if (parent != nullptr) { + parent->add(t); + } return t; } @@ -505,7 +507,7 @@ public: void visit(InstructionSequence *sequence) { Q_ASSERT(sequence); - Q_FOREACH (Instruction *instruction, *sequence) { + for (Instruction *instruction : qAsConst(*sequence)) { Q_ASSERT(instruction); instruction->accept(this); } @@ -513,7 +515,7 @@ public: void visit(const QVector<DataElement *> &dataElements) { - Q_FOREACH (DataElement *dataElement, dataElements) { + for (DataElement *dataElement : dataElements) { Q_ASSERT(dataElement); dataElement->accept(this); } @@ -521,7 +523,7 @@ public: void visit(const QVector<StateOrTransition *> &children) { - Q_FOREACH (StateOrTransition *child, children) { + for (StateOrTransition *child : children) { Q_ASSERT(child); child->accept(this); } @@ -529,7 +531,7 @@ public: void visit(const InstructionSequences &sequences) { - Q_FOREACH (InstructionSequence *sequence, sequences) { + for (InstructionSequence *sequence : sequences) { Q_ASSERT(sequence); visit(sequence); } @@ -537,25 +539,21 @@ public: void visit(const QVector<Param *> ¶ms) { - Q_FOREACH (Param *param, params) { + for (Param *param : params) { Q_ASSERT(param); param->accept(this); } } }; -Q_SCXML_EXPORT bool isValidCppIdentifier(const QString &str); -Q_SCXML_EXPORT bool isValidQPropertyName(const QString &str); -Q_SCXML_EXPORT bool isEventToBeGenerated(const QString &event); - } // DocumentModel namespace -class Q_SCXML_EXPORT QScxmlParserPrivate +class Q_SCXML_EXPORT QScxmlCompilerPrivate { public: - static QScxmlParserPrivate *get(QScxmlParser *parser); + static QScxmlCompilerPrivate *get(QScxmlCompiler *compiler); - QScxmlParserPrivate(QScxmlParser *parser, QXmlStreamReader *reader); + QScxmlCompilerPrivate(QXmlStreamReader *reader); bool verifyDocument(); DocumentModel::ScxmlDocument *scxmlDocument() const; @@ -563,21 +561,24 @@ public: QString fileName() const; void setFileName(const QString &fileName); - QScxmlParser::Loader *loader() const; - void setLoader(QScxmlParser::Loader *loader); + QScxmlCompiler::Loader *loader() const; + void setLoader(QScxmlCompiler::Loader *loader); bool readDocument(); - void parseSubDocument(DocumentModel::Invoke *parentInvoke, QXmlStreamReader *reader, const QString &fileName); - bool parseSubElement(DocumentModel::Invoke *parentInvoke, QXmlStreamReader *reader, const QString &fileName); - QByteArray load(const QString &name, bool *ok) const; + void parseSubDocument(DocumentModel::Invoke *parentInvoke, + QXmlStreamReader *reader, + const QString &fileName); + bool parseSubElement(DocumentModel::Invoke *parentInvoke, + QXmlStreamReader *reader, + const QString &fileName); + QByteArray load(const QString &name, bool *ok); QVector<QScxmlError> errors() const; void addError(const QString &msg); void addError(const DocumentModel::XmlLocation &location, const QString &msg); - - QScxmlParser::QtMode qtMode() const; - void setQtMode(QScxmlParser::QtMode mode); + QScxmlStateMachine *instantiateStateMachine() const; + void instantiateDataModel(QScxmlStateMachine *stateMachine) const; private: DocumentModel::AbstractState *currentParent() const; @@ -645,7 +646,6 @@ private: bool readElement(); void resetDocument(); - void parseComment(); void currentStateUp(); bool flushInstruction(); @@ -698,14 +698,18 @@ private: static QStringList optionalAttributes(Kind kind); }; - class DefaultLoader: public QScxmlParser::Loader +public: + class DefaultLoader: public QScxmlCompiler::Loader { public: - DefaultLoader(QScxmlParser *parser); - QByteArray load(const QString &name, const QString &baseDir, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; + DefaultLoader(); + QByteArray load(const QString &name, + const QString &baseDir, + QStringList *errors) Q_DECL_OVERRIDE Q_DECL_FINAL; }; - bool checkAttributes(const QXmlStreamAttributes &attributes, QScxmlParserPrivate::ParserState::Kind kind); +private: + bool checkAttributes(const QXmlStreamAttributes &attributes, QScxmlCompilerPrivate::ParserState::Kind kind); ParserState ¤t(); ParserState &previous(); bool hasPrevious() const; @@ -717,14 +721,13 @@ private: QScopedPointer<DocumentModel::ScxmlDocument> m_doc; DocumentModel::StateContainer *m_currentState; DefaultLoader m_defaultLoader; - QScxmlParser::Loader *m_loader; + QScxmlCompiler::Loader *m_loader; QXmlStreamReader *m_reader; QVector<ParserState> m_stack; QVector<QScxmlError> m_errors; - QScxmlParser::QtMode m_qtMode; }; QT_END_NAMESPACE -#endif // SCXMLPARSER_P_H +#endif // QSCXMLCOMPILER_P_H diff --git a/src/scxml/qscxmlcppdatamodel.cpp b/src/scxml/qscxmlcppdatamodel.cpp index 15be429..bc09d65 100644 --- a/src/scxml/qscxmlcppdatamodel.cpp +++ b/src/scxml/qscxmlcppdatamodel.cpp @@ -92,7 +92,7 @@ class TheDataModel: public QScxmlCppDataModel <script> media = eventData().value(QStringLiteral("media")).toString(); </script> - <send type="qt:signal" event="playbackStarted"> + <send event="playbackStarted"> <param name="media" expr="media"/> </send> </onentry> @@ -239,7 +239,7 @@ bool QScxmlCppDataModel::setScxmlProperty(const QString &name, const QVariant &v * Returns \c true if the state machine is in the state specified by \a stateName, \c false * otherwise. */ -bool QScxmlCppDataModel::In(const QString &stateName) const +bool QScxmlCppDataModel::inState(const QString &stateName) const { return stateMachine()->isActive(stateName); } diff --git a/src/scxml/qscxmlcppdatamodel.h b/src/scxml/qscxmlcppdatamodel.h index 328dee8..fb59336 100644 --- a/src/scxml/qscxmlcppdatamodel.h +++ b/src/scxml/qscxmlcppdatamodel.h @@ -61,7 +61,7 @@ public: explicit QScxmlCppDataModel(QObject *parent = nullptr); ~QScxmlCppDataModel(); - bool setup(const QVariantMap &initialDataValues) Q_DECL_OVERRIDE; + Q_INVOKABLE bool setup(const QVariantMap &initialDataValues) Q_DECL_OVERRIDE; #ifndef Q_QDOC void evaluateAssignment(QScxmlExecutableContent::EvaluatorId id, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; @@ -76,7 +76,7 @@ public: bool hasScxmlProperty(const QString &name) const Q_DECL_OVERRIDE; bool setScxmlProperty(const QString &name, const QVariant &value, const QString &context) Q_DECL_OVERRIDE; - bool In(const QString &stateName) const; + bool inState(const QString &stateName) const; }; QT_END_NAMESPACE diff --git a/src/scxml/qscxmldatamodel.h b/src/scxml/qscxmldatamodel.h index d3ade56..845d8bf 100644 --- a/src/scxml/qscxmldatamodel.h +++ b/src/scxml/qscxmldatamodel.h @@ -74,7 +74,7 @@ public: void setStateMachine(QScxmlStateMachine *stateMachine); QScxmlStateMachine *stateMachine() const; - virtual bool setup(const QVariantMap &initialDataValues) = 0; + Q_INVOKABLE virtual bool setup(const QVariantMap &initialDataValues) = 0; #ifndef Q_QDOC virtual QString evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) = 0; diff --git a/src/scxml/qscxmldatamodel_p.h b/src/scxml/qscxmldatamodel_p.h index 1fdcfbb..089071a 100644 --- a/src/scxml/qscxmldatamodel_p.h +++ b/src/scxml/qscxmldatamodel_p.h @@ -52,7 +52,7 @@ // #include "qscxmldatamodel.h" -#include "qscxmlparser_p.h" +#include "qscxmlcompiler_p.h" #include <private/qobject_p.h> QT_BEGIN_NAMESPACE diff --git a/src/scxml/qscxmlecmascriptdatamodel.cpp b/src/scxml/qscxmlecmascriptdatamodel.cpp index ceba661..32c49f0 100644 --- a/src/scxml/qscxmlecmascriptdatamodel.cpp +++ b/src/scxml/qscxmlecmascriptdatamodel.cpp @@ -91,7 +91,7 @@ public: QJSValue evalJSValue(const QString &expr, const QString &context, bool *ok) { - Q_ASSERT(engine()); + assertEngine(); QString script = QStringLiteral("(function(){'use strict'; return (\n%1\n); })()").arg(expr); return eval(script, context, ok); @@ -100,11 +100,11 @@ public: QJSValue eval(const QString &script, const QString &context, bool *ok) { Q_ASSERT(ok); - Q_ASSERT(engine()); + QJSEngine *engine = assertEngine(); // TODO: copy QJSEngine::evaluate and handle the case of v4->catchException() "our way" - QJSValue v = engine()->evaluate(QStringLiteral("'use strict'; ") + script, QStringLiteral("<expr>"), 0); + QJSValue v = engine->evaluate(QStringLiteral("'use strict'; ") + script, QStringLiteral("<expr>"), 0); if (v.isError()) { *ok = false; submitError(QStringLiteral("error.execution"), @@ -118,8 +118,8 @@ public: void setupDataModel() { - Q_ASSERT(engine()); - dataModel = engine()->globalObject(); + QJSEngine *engine = assertEngine(); + dataModel = engine->globalObject(); qCDebug(qscxmlLog) << stateMachine() << "initializing the datamodel"; setupSystemVariables(); @@ -132,17 +132,18 @@ public: setReadonlyProperty(&dataModel, QStringLiteral("_name"), stateMachine()->name()); - auto scxml = engine()->newObject(); + QJSEngine *engine = assertEngine(); + auto scxml = engine->newObject(); scxml.setProperty(QStringLiteral("location"), QStringLiteral("#_scxml_%1").arg(stateMachine()->sessionId())); - auto ioProcs = engine()->newObject(); + auto ioProcs = engine->newObject(); setReadonlyProperty(&ioProcs, QStringLiteral("scxml"), scxml); setReadonlyProperty(&dataModel, QStringLiteral("_ioprocessors"), ioProcs); - auto platformVars = QScxmlPlatformProperties::create(engine(), stateMachine()); + auto platformVars = QScxmlPlatformProperties::create(engine, stateMachine()); dataModel.setProperty(QStringLiteral("_x"), platformVars->jsValue()); - dataModel.setProperty(QStringLiteral("In"), - engine()->evaluate(QStringLiteral("function(id){return _x.In(id);}"))); + dataModel.setProperty(QStringLiteral("In"), engine->evaluate( + QStringLiteral("function(id){return _x.inState(id);}"))); } void assignEvent(const QScxmlEvent &event) @@ -150,20 +151,21 @@ public: if (event.name().isEmpty()) return; - QJSValue _event = engine()->newObject(); + QJSEngine *engine = assertEngine(); + QJSValue _event = engine->newObject(); QJSValue dataValue = eventDataAsJSValue(event.data()); _event.setProperty(QStringLiteral("data"), dataValue.isUndefined() ? QJSValue(QJSValue::UndefinedValue) : dataValue); _event.setProperty(QStringLiteral("invokeid"), event.invokeId().isEmpty() ? QJSValue(QJSValue::UndefinedValue) - : engine()->toScriptValue(event.invokeId())); + : engine->toScriptValue(event.invokeId())); if (!event.originType().isEmpty()) - _event.setProperty(QStringLiteral("origintype"), engine()->toScriptValue(event.originType())); + _event.setProperty(QStringLiteral("origintype"), engine->toScriptValue(event.originType())); _event.setProperty(QStringLiteral("origin"), event.origin().isEmpty() ? QJSValue(QJSValue::UndefinedValue) - : engine()->toScriptValue(event.origin()) ); + : engine->toScriptValue(event.origin()) ); _event.setProperty(QStringLiteral("sendid"), event.sendId().isEmpty() ? QJSValue(QJSValue::UndefinedValue) - : engine()->toScriptValue(event.sendId())); - _event.setProperty(QStringLiteral("type"), engine()->toScriptValue(event.scxmlType())); - _event.setProperty(QStringLiteral("name"), engine()->toScriptValue(event.name())); + : engine->toScriptValue(event.sendId())); + _event.setProperty(QStringLiteral("type"), engine->toScriptValue(event.scxmlType())); + _event.setProperty(QStringLiteral("name"), engine->toScriptValue(event.name())); _event.setProperty(QStringLiteral("raw"), QStringLiteral("unsupported")); // See test178 if (event.isErrorEvent()) _event.setProperty(QStringLiteral("errorMessage"), event.errorMessage()); @@ -171,18 +173,19 @@ public: setReadonlyProperty(&dataModel, QStringLiteral("_event"), _event); } - QJSValue eventDataAsJSValue(const QVariant &eventData) const + QJSValue eventDataAsJSValue(const QVariant &eventData) { if (!eventData.isValid()) { return QJSValue(QJSValue::UndefinedValue); } + QJSEngine *engine = assertEngine(); if (eventData.canConvert<QVariantMap>()) { auto keyValues = eventData.value<QVariantMap>(); - auto data = engine()->newObject(); + auto data = engine->newObject(); for (QVariantMap::const_iterator it = keyValues.begin(), eit = keyValues.end(); it != eit; ++it) { - data.setProperty(it.key(), engine()->toScriptValue(it.value())); + data.setProperty(it.key(), engine->toScriptValue(it.value())); } return data; @@ -196,9 +199,9 @@ public: QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8(), &err); if (err.error == QJsonParseError::NoError) - return engine()->toScriptValue(doc.toVariant()); + return engine->toScriptValue(doc.toVariant()); else - return engine()->toScriptValue(data); + return engine->toScriptValue(data); } QScxmlStateMachine *stateMachine() const @@ -207,15 +210,21 @@ public: return q->stateMachine(); } - QJSEngine *engine() const + QJSEngine *assertEngine() { - if (jsEngine == Q_NULLPTR) { - jsEngine = new QJSEngine(stateMachine()); + if (!jsEngine) { + Q_Q(QScxmlEcmaScriptDataModel); + setEngine(new QJSEngine(q->stateMachine())); } return jsEngine; } + QJSEngine *engine() const + { + return jsEngine; + } + void setEngine(QJSEngine *engine) { jsEngine = engine; } @@ -225,9 +234,6 @@ public: return q->tableData()->string(id); } - QScxmlStateMachine::BindingMethod dataBinding() const - { return stateMachine()->dataBinding(); } - bool hasProperty(const QString &name) const { return dataModel.hasProperty(name); } @@ -339,7 +345,7 @@ private: // Uses private API } private: - mutable QJSEngine *jsEngine; + QJSEngine *jsEngine; QJSValue dataModel; }; @@ -386,7 +392,8 @@ bool QScxmlEcmaScriptDataModel::setup(const QVariantMap &initialDataValues) QJSValue v = undefined; QVariantMap::const_iterator it = initialDataValues.find(name); if (it != initialDataValues.end()) { - v = d->engine()->toScriptValue(it.value()); + QJSEngine *engine = d->assertEngine(); + v = engine->toScriptValue(it.value()); } if (!d->setProperty(name, v, QStringLiteral("<data>"))) { ok = false; @@ -477,7 +484,9 @@ bool QScxmlEcmaScriptDataModel::evaluateForeach(EvaluatorId id, bool *ok, Foreac } QString item = d->string(info.item); - if (engine()->evaluate(QStringLiteral("(function(){var %1 = 0})()").arg(item)).isError()) { + + QJSEngine *engine = d->assertEngine(); + if (engine->evaluate(QStringLiteral("(function(){var %1 = 0})()").arg(item)).isError()) { d->submitError(QStringLiteral("error.execution"), QStringLiteral("invalid item '%1' in %2") .arg(d->string(info.item), d->string(info.context))); *ok = false; @@ -541,27 +550,11 @@ bool QScxmlEcmaScriptDataModel::setScxmlProperty(const QString &name, const QVar { Q_D(QScxmlEcmaScriptDataModel); Q_ASSERT(hasScxmlProperty(name)); - QJSValue v = d->engine()->toScriptValue( + + QJSEngine *engine = d->assertEngine(); + QJSValue v = engine->toScriptValue( value.canConvert<QJSValue>() ? value.value<QJSValue>().toVariant() : value); return d->setProperty(name, v, context); } -/*! - * Returns the JavaScript engine used by this data model. - */ -QJSEngine *QScxmlEcmaScriptDataModel::engine() const -{ - Q_D(const QScxmlEcmaScriptDataModel); - return d->engine(); -} - -/*! - * Sets the JavaScript engine used by this data model to \a engine. - */ -void QScxmlEcmaScriptDataModel::setEngine(QJSEngine *engine) -{ - Q_D(QScxmlEcmaScriptDataModel); - d->setEngine(engine); -} - QT_END_NAMESPACE diff --git a/src/scxml/qscxmlecmascriptdatamodel.h b/src/scxml/qscxmlecmascriptdatamodel.h index 4c484f2..b1d9f55 100644 --- a/src/scxml/qscxmlecmascriptdatamodel.h +++ b/src/scxml/qscxmlecmascriptdatamodel.h @@ -54,7 +54,7 @@ public: explicit QScxmlEcmaScriptDataModel(QObject *parent = nullptr); ~QScxmlEcmaScriptDataModel(); - bool setup(const QVariantMap &initialDataValues) Q_DECL_OVERRIDE; + Q_INVOKABLE bool setup(const QVariantMap &initialDataValues) Q_DECL_OVERRIDE; #ifndef Q_QDOC QString evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; @@ -71,9 +71,6 @@ public: QVariant scxmlProperty(const QString &name) const Q_DECL_OVERRIDE; bool hasScxmlProperty(const QString &name) const Q_DECL_OVERRIDE; bool setScxmlProperty(const QString &name, const QVariant &value, const QString &context) Q_DECL_OVERRIDE; - - QJSEngine *engine() const; - void setEngine(QJSEngine *engine); }; QT_END_NAMESPACE diff --git a/src/scxml/qscxmlecmascriptplatformproperties.cpp b/src/scxml/qscxmlecmascriptplatformproperties.cpp index 8433d78..41e27b5 100644 --- a/src/scxml/qscxmlecmascriptplatformproperties.cpp +++ b/src/scxml/qscxmlecmascriptplatformproperties.cpp @@ -93,7 +93,7 @@ QString QScxmlPlatformProperties::marks() const return QStringLiteral("the spot"); } -bool QScxmlPlatformProperties::In(const QString &stateName) +bool QScxmlPlatformProperties::inState(const QString &stateName) { return stateMachine()->isActive(stateName); } diff --git a/src/scxml/qscxmlecmascriptplatformproperties_p.h b/src/scxml/qscxmlecmascriptplatformproperties_p.h index 75f9b1c..fef53ea 100644 --- a/src/scxml/qscxmlecmascriptplatformproperties_p.h +++ b/src/scxml/qscxmlecmascriptplatformproperties_p.h @@ -77,7 +77,7 @@ public: QString marks() const; - Q_INVOKABLE bool In(const QString &stateName); + Q_INVOKABLE bool inState(const QString &stateName); private: class Data; diff --git a/src/scxml/qscxmlerror.cpp b/src/scxml/qscxmlerror.cpp index 949ea9f..84cf4fe 100644 --- a/src/scxml/qscxmlerror.cpp +++ b/src/scxml/qscxmlerror.cpp @@ -62,10 +62,35 @@ public: * \since 5.7 * \inmodule QtScxml * - * \sa QScxmlStateMachine QScxmlParser + * \sa QScxmlStateMachine QScxmlCompiler */ /*! + \property QScxmlError::column + \brief The column number in which the SCXML error occurred. +*/ + +/*! + \property QScxmlError::description + \brief A description of the SCXML error. +*/ + +/*! + \property QScxmlError::fileName + \brief The name of the file in which the SCXML error occurred. +*/ + +/*! + \property QScxmlError::line + \brief The line number on which the SCXML error occurred. +*/ + +/*! + \property QScxmlError::valid + \brief Whether the SCXML error is valid. +*/ + +/*! * Creates a new invalid SCXML error. */ QScxmlError::QScxmlError() diff --git a/src/scxml/qscxmlerror.h b/src/scxml/qscxmlerror.h index 80325eb..cae249e 100644 --- a/src/scxml/qscxmlerror.h +++ b/src/scxml/qscxmlerror.h @@ -41,6 +41,7 @@ #define QSCXMLERROR_H #include <QtScxml/qscxmlglobals.h> +#include <QtCore/qobjectdefs.h> #include <QString> @@ -48,6 +49,15 @@ QT_BEGIN_NAMESPACE class Q_SCXML_EXPORT QScxmlError { +#ifndef BUILD_QSCXMLC + Q_GADGET + Q_PROPERTY(bool valid READ isValid CONSTANT) + Q_PROPERTY(QString fileName READ fileName CONSTANT) + Q_PROPERTY(int line READ line CONSTANT) + Q_PROPERTY(int column READ column CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) +#endif // BUILD_QSCXMLC + public: QScxmlError(); QScxmlError(const QString &fileName, int line, int column, const QString &description); diff --git a/src/scxml/qscxmlevent.cpp b/src/scxml/qscxmlevent.cpp index bf5de7a..6da52d7 100644 --- a/src/scxml/qscxmlevent.cpp +++ b/src/scxml/qscxmlevent.cpp @@ -48,9 +48,6 @@ QT_BEGIN_NAMESPACE using namespace QScxmlExecutableContent; -QEvent::Type QScxmlEvent::scxmlEventType = (QEvent::Type) QEvent::registerEventType(); -QEvent::Type QScxmlEvent::ignoreEventType = (QEvent::Type) QEvent::registerEventType(); - QAtomicInt QScxmlEventBuilder::idCounter = QAtomicInt(0); QScxmlEvent *QScxmlEventBuilder::buildEvent() @@ -140,7 +137,6 @@ QScxmlEvent *QScxmlEventBuilder::buildEvent() return Q_NULLPTR; } if (!origintype.isEmpty() - && origintype != QStringLiteral("qt:signal") && origintype != QStringLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor")) { // [6.2.5] and test199 submitError(QStringLiteral("error.execution"), @@ -180,7 +176,7 @@ QScxmlEvent *QScxmlEventBuilder::errorEvent(QScxmlStateMachine *stateMachine, co return error; } -bool QScxmlEventBuilder::evaluate(const Param ¶m, QScxmlStateMachine *stateMachine, +bool QScxmlEventBuilder::evaluate(const ParameterInfo ¶m, QScxmlStateMachine *stateMachine, QVariantMap &keyValues) { auto dataModel = stateMachine->dataModel(); @@ -212,7 +208,8 @@ bool QScxmlEventBuilder::evaluate(const Param ¶m, QScxmlStateMachine *stateM } } -bool QScxmlEventBuilder::evaluate(const QScxmlExecutableContent::Array<Param> *params, QScxmlStateMachine *stateMachine, QVariantMap &keyValues) +bool QScxmlEventBuilder::evaluate(const QScxmlExecutableContent::Array<ParameterInfo> *params, + QScxmlStateMachine *stateMachine, QVariantMap &keyValues) { if (!params) return true; @@ -267,7 +264,7 @@ void QScxmlEventBuilder::submitError(const QString &type, const QString &msg, co * Creates a new external SCXML event. */ QScxmlEvent::QScxmlEvent() - : QEvent(scxmlEventType), d(new QScxmlEventPrivate) + : d(new QScxmlEventPrivate) { } /*! @@ -279,6 +276,12 @@ QScxmlEvent::~QScxmlEvent() } /*! + \property QScxmlEvent::scxmlType + \brief The event type. + +*/ + +/*! * Returns the event type. */ QString QScxmlEvent::scxmlType() const @@ -308,7 +311,6 @@ void QScxmlEvent::clear() */ QScxmlEvent &QScxmlEvent::operator=(const QScxmlEvent &other) { - QEvent::operator=(other); *d = *other.d; return *this; } @@ -317,7 +319,7 @@ QScxmlEvent &QScxmlEvent::operator=(const QScxmlEvent &other) * Constructs a copy of \a other. */ QScxmlEvent::QScxmlEvent(const QScxmlEvent &other) - : QEvent(other), d(new QScxmlEventPrivate(*other.d)) + : d(new QScxmlEventPrivate(*other.d)) { } @@ -460,6 +462,13 @@ void QScxmlEvent::setInvokeId(const QString &invokeid) } /*! + \property QScxmlEvent::delay + + \brief The delay in milliseconds after which the event is to be delivered + after processing the \c <send> element. +*/ + +/*! * Returns the delay in milliseconds after which this event is to be delivered * after processing the \c <send> element. */ @@ -535,6 +544,11 @@ void QScxmlEvent::setData(const QVariant &data) } /*! + \property QScxmlEvent::errorEvent + \brief Whether the event represents an error. +*/ + +/*! * Returns \c true when this is an error event, \c false otherwise. */ bool QScxmlEvent::isErrorEvent() const @@ -543,6 +557,11 @@ bool QScxmlEvent::isErrorEvent() const } /*! + \property QScxmlEvent::errorMessage + \brief An error message for an error event, or an empty QString. +*/ + +/*! * If this is an error event, returns the error message. Otherwise, returns an * empty QString. */ @@ -562,11 +581,6 @@ void QScxmlEvent::setErrorMessage(const QString &message) d->data = message; } -void QScxmlEvent::makeIgnorable() -{ - t = ignoreEventType; -} - QByteArray QScxmlEventPrivate::debugString(QScxmlEvent *event) { if (event == nullptr) { diff --git a/src/scxml/qscxmlevent.h b/src/scxml/qscxmlevent.h index 82efc57..c4f517f 100644 --- a/src/scxml/qscxmlevent.h +++ b/src/scxml/qscxmlevent.h @@ -42,28 +42,27 @@ #include <QtScxml/qscxmlglobals.h> -#include <QEvent> #include <QStringList> #include <QVariantList> QT_BEGIN_NAMESPACE -namespace QScxmlInternal { -class WrappedQStateMachine; -} - class QScxmlEventPrivate; -class Q_SCXML_EXPORT QScxmlEvent: public QEvent +class Q_SCXML_EXPORT QScxmlEvent { Q_GADGET - Q_PROPERTY(QString name READ name CONSTANT) - Q_PROPERTY(QString eventType READ scxmlType CONSTANT) - Q_PROPERTY(QString sendId READ sendId CONSTANT) - Q_PROPERTY(QString origin READ origin CONSTANT) - Q_PROPERTY(QString originType READ originType CONSTANT) - Q_PROPERTY(QString invokeId READ invokeId CONSTANT) - Q_PROPERTY(QVariant data READ data CONSTANT) + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(EventType eventType READ eventType WRITE setEventType) + Q_PROPERTY(QString scxmlType READ scxmlType) + Q_PROPERTY(QString sendId READ sendId WRITE setSendId) + Q_PROPERTY(QString origin READ origin WRITE setOrigin) + Q_PROPERTY(QString originType READ originType WRITE setOriginType) + Q_PROPERTY(QString invokeId READ invokeId WRITE setInvokeId) + Q_PROPERTY(int delay READ delay WRITE setDelay) + Q_PROPERTY(QVariant data READ data WRITE setData) + Q_PROPERTY(bool errorEvent READ isErrorEvent) + Q_PROPERTY(QString errorMessage READ errorMessage WRITE setErrorMessage) public: QScxmlEvent(); @@ -77,6 +76,7 @@ public: InternalEvent, ExternalEvent }; + Q_ENUM(EventType) QString name() const; void setName(const QString &name); @@ -101,7 +101,7 @@ public: int delay() const; void setDelay(int delayInMiliSecs); - void clear(); + Q_INVOKABLE void clear(); QVariant data() const; void setData(const QVariant &data); @@ -110,14 +110,6 @@ public: QString errorMessage() const; void setErrorMessage(const QString &message); -protected: - friend class QScxmlInternal::WrappedQStateMachine; -#ifndef Q_QDOC - static QEvent::Type scxmlEventType; - static QEvent::Type ignoreEventType; - void makeIgnorable(); -#endif // Q_QDOC - private: QScxmlEventPrivate *d; diff --git a/src/scxml/qscxmlevent_p.h b/src/scxml/qscxmlevent_p.h index d8d1361..aef14d1 100644 --- a/src/scxml/qscxmlevent_p.h +++ b/src/scxml/qscxmlevent_p.h @@ -71,7 +71,7 @@ class QScxmlEventBuilder QScxmlExecutableContent::EvaluatorId eventexpr; QString contents; QScxmlExecutableContent::EvaluatorId contentExpr; - const QScxmlExecutableContent::Array<QScxmlExecutableContent::Param> *params; + const QScxmlExecutableContent::Array<QScxmlExecutableContent::ParameterInfo> *params; QScxmlEvent::EventType eventType; QString id; QString idLocation; @@ -118,7 +118,7 @@ public: eventType = QScxmlEvent::InternalEvent; } - QScxmlEventBuilder(QScxmlStateMachine *stateMachine, QScxmlExecutableContent::Send &send) + QScxmlEventBuilder(QScxmlStateMachine *stateMachine, const QScxmlExecutableContent::Send &send) { init(); this->stateMachine = stateMachine; @@ -126,6 +126,7 @@ public: event = stateMachine->tableData()->string(send.event); eventexpr = send.eventexpr; contents = stateMachine->tableData()->string(send.content); + contentExpr = send.contentexpr; params = send.params(); id = stateMachine->tableData()->string(send.id); idLocation = stateMachine->tableData()->string(send.idLocation); @@ -143,12 +144,13 @@ public: static QScxmlEvent *errorEvent(QScxmlStateMachine *stateMachine, const QString &name, const QString &message, const QString &sendid); - bool evaluate(const QScxmlExecutableContent::Param ¶m, QScxmlStateMachine *stateMachine, - QVariantMap &keyValues); - - bool evaluate(const QScxmlExecutableContent::Array<QScxmlExecutableContent::Param> *params, + bool evaluate(const QScxmlExecutableContent::ParameterInfo ¶m, QScxmlStateMachine *stateMachine, QVariantMap &keyValues); + bool evaluate( + const QScxmlExecutableContent::Array<QScxmlExecutableContent::ParameterInfo> *params, + QScxmlStateMachine *stateMachine, QVariantMap &keyValues); + void submitError(const QString &type, const QString &msg, const QString &sendid = QString()); }; #endif // BUILD_QSCXMLC diff --git a/src/scxml/qscxmlexecutablecontent.cpp b/src/scxml/qscxmlexecutablecontent.cpp index a11b4ca..5df8b1f 100644 --- a/src/scxml/qscxmlexecutablecontent.cpp +++ b/src/scxml/qscxmlexecutablecontent.cpp @@ -39,7 +39,7 @@ #include "qscxmlglobals_p.h" #include "qscxmlexecutablecontent_p.h" -#include "qscxmlparser_p.h" +#include "qscxmlcompiler_p.h" #include "qscxmlevent_p.h" QT_BEGIN_NAMESPACE @@ -96,64 +96,69 @@ bool QScxmlExecutionEngine::execute(ContainerId id, const QVariant &extraData) if (id == NoInstruction) return true; - qint32 *ip = stateMachine->tableData()->instructions() + id; + const InstructionId *ip = stateMachine->tableData()->instructions() + id; this->extraData = extraData; - bool result = step(ip); + bool result = true; + step(ip, &result); this->extraData = QVariant(); return result; } -bool QScxmlExecutionEngine::step(Instructions &ip) +const InstructionId *QScxmlExecutionEngine::step(const InstructionId *ip, bool *ok) { auto dataModel = stateMachine->dataModel(); auto tableData = stateMachine->tableData(); - auto instr = reinterpret_cast<Instruction *>(ip); + *ok = true; + auto instr = reinterpret_cast<const Instruction *>(ip); switch (instr->instructionType) { case Instruction::Sequence: { qCDebug(qscxmlLog) << stateMachine << "Executing sequence step"; - InstructionSequence *sequence = reinterpret_cast<InstructionSequence *>(instr); + const InstructionSequence *sequence = reinterpret_cast<const InstructionSequence *>(instr); ip = sequence->instructions(); - Instructions end = ip + sequence->entryCount; + const InstructionId *end = ip + sequence->entryCount; while (ip < end) { - if (!step(ip)) { - ip = end; + ip = step(ip, ok); + if (!(*ok)) { qCDebug(qscxmlLog) << stateMachine << "Finished sequence step UNsuccessfully"; - return false; + return end; } } qCDebug(qscxmlLog) << stateMachine << "Finished sequence step successfully"; - return true; + return ip; } case Instruction::Sequences: { qCDebug(qscxmlLog) << stateMachine << "Executing sequences step"; - InstructionSequences *sequences = reinterpret_cast<InstructionSequences *>(instr); + const InstructionSequences *sequences + = reinterpret_cast<const InstructionSequences *>(instr); ip += sequences->size(); for (int i = 0; i != sequences->sequenceCount; ++i) { - Instructions sequence = sequences->at(i); - step(sequence); + bool ignored; + const InstructionId *sequence = sequences->at(i); + step(sequence, &ignored); } qCDebug(qscxmlLog) << stateMachine << "Finished sequences step"; - return true; + return ip; } case Instruction::Send: { qCDebug(qscxmlLog) << stateMachine << "Executing send step"; - Send *send = reinterpret_cast<Send *>(instr); + const Send *send = reinterpret_cast<const Send *>(instr); ip += send->size(); QString delay = tableData->string(send->delay); if (send->delayexpr != NoEvaluator) { - bool ok = false; - delay = stateMachine->dataModel()->evaluateToString(send->delayexpr, &ok); - if (!ok) - return false; + delay = stateMachine->dataModel()->evaluateToString(send->delayexpr, ok); + if (!(*ok)) + return ip; } QScxmlEvent *event = QScxmlEventBuilder(stateMachine, *send).buildEvent(); - if (!event) - return false; + if (!event) { + *ok = false; + return ip; + } if (!delay.isEmpty()) { int msecs = parseTime(delay); @@ -161,94 +166,91 @@ bool QScxmlExecutionEngine::step(Instructions &ip) event->setDelay(msecs); } else { qCDebug(qscxmlLog) << stateMachine << "failed to parse delay time" << delay; - return false; + *ok = false; + return ip; } } stateMachine->submitEvent(event); - return true; + return ip; } case Instruction::JavaScript: { qCDebug(qscxmlLog) << stateMachine << "Executing script step"; - JavaScript *javascript = reinterpret_cast<JavaScript *>(instr); + const JavaScript *javascript = reinterpret_cast<const JavaScript *>(instr); ip += javascript->size(); - bool ok = true; - dataModel->evaluateToVoid(javascript->go, &ok); - return ok; + dataModel->evaluateToVoid(javascript->go, ok); + return ip; } case Instruction::If: { qCDebug(qscxmlLog) << stateMachine << "Executing if step"; - If *_if = reinterpret_cast<If *>(instr); + const If *_if = reinterpret_cast<const If *>(instr); ip += _if->size(); auto blocks = _if->blocks(); for (qint32 i = 0; i < _if->conditions.count; ++i) { - bool ok = true; - if (dataModel->evaluateToBool(_if->conditions.at(i), &ok) && ok) { - Instructions block = blocks->at(i); - bool res = step(block); + bool conditionOk = true; + if (dataModel->evaluateToBool(_if->conditions.at(i), &conditionOk) && conditionOk) { + const InstructionId *block = blocks->at(i); + step(block, ok); qCDebug(qscxmlLog) << stateMachine << "Finished if step"; - return res; + return ip; } } - if (_if->conditions.count < blocks->sequenceCount) { - Instructions block = blocks->at(_if->conditions.count); - return step(block); - } + if (_if->conditions.count < blocks->sequenceCount) + step(blocks->at(_if->conditions.count), ok); - return true; + return ip; } case Instruction::Foreach: { class LoopBody: public QScxmlDataModel::ForeachLoopBody // If only we could put std::function in public API, we could use a lambda here. Alas.... { QScxmlExecutionEngine *engine; - const Instructions loopStart; + const InstructionId *loopStart; public: - LoopBody(QScxmlExecutionEngine *engine, const Instructions loopStart) + LoopBody(QScxmlExecutionEngine *engine, const InstructionId *loopStart) : engine(engine) , loopStart(loopStart) {} bool run() Q_DECL_OVERRIDE { - Instructions ip = loopStart; - return engine->step(ip); + bool ok = true; + engine->step(loopStart, &ok); + return ok; } }; qCDebug(qscxmlLog) << stateMachine << "Executing foreach step"; - Foreach *foreach = reinterpret_cast<Foreach *>(instr); - Instructions loopStart = foreach->blockstart(); - ip += foreach->size(); - bool ok = true; + const Foreach *_foreach = reinterpret_cast<const Foreach *>(instr); + const InstructionId *loopStart = _foreach->blockstart(); + ip += _foreach->size(); LoopBody body(this, loopStart); - bool evenMoreOk = dataModel->evaluateForeach(foreach->doIt, &ok, &body); - return ok && evenMoreOk; + *ok = dataModel->evaluateForeach(_foreach->doIt, ok, &body) && *ok; + return ip; } case Instruction::Raise: { qCDebug(qscxmlLog) << stateMachine << "Executing raise step"; - Raise *raise = reinterpret_cast<Raise *>(instr); + const Raise *raise = reinterpret_cast<const Raise *>(instr); ip += raise->size(); auto name = tableData->string(raise->event); auto event = new QScxmlEvent; event->setName(name); event->setEventType(QScxmlEvent::InternalEvent); stateMachine->submitEvent(event); - return true; + return ip; } case Instruction::Log: { qCDebug(qscxmlLog) << stateMachine << "Executing log step"; - Log *log = reinterpret_cast<Log *>(instr); + const Log *log = reinterpret_cast<const Log *>(instr); ip += log->size(); - bool ok = true; - QString str = dataModel->evaluateToString(log->expr, &ok); - if (ok) { + QString str = dataModel->evaluateToString(log->expr, ok); + if (*ok) { const QString label = tableData->string(log->label); qCDebug(scxmlLog) << label << ":" << str; QMetaObject::invokeMethod(stateMachine, @@ -257,411 +259,56 @@ bool QScxmlExecutionEngine::step(Instructions &ip) Q_ARG(QString, label), Q_ARG(QString, str)); } - return ok; + return ip; } case Instruction::Cancel: { qCDebug(qscxmlLog) << stateMachine << "Executing cancel step"; - Cancel *cancel = reinterpret_cast<Cancel *>(instr); + const Cancel *cancel = reinterpret_cast<const Cancel *>(instr); ip += cancel->size(); QString e = tableData->string(cancel->sendid); - bool ok = true; if (cancel->sendidexpr != NoEvaluator) - e = dataModel->evaluateToString(cancel->sendidexpr, &ok); - if (ok && !e.isEmpty()) + e = dataModel->evaluateToString(cancel->sendidexpr, ok); + if (*ok && !e.isEmpty()) stateMachine->cancelDelayedEvent(e); - return ok; + return ip; } case Instruction::Assign: { qCDebug(qscxmlLog) << stateMachine << "Executing assign step"; - Assign *assign = reinterpret_cast<Assign *>(instr); + const Assign *assign = reinterpret_cast<const Assign *>(instr); ip += assign->size(); - bool ok = true; - dataModel->evaluateAssignment(assign->expression, &ok); - return ok; + dataModel->evaluateAssignment(assign->expression, ok); + return ip; } case Instruction::Initialize: { qCDebug(qscxmlLog) << stateMachine << "Executing initialize step"; - Initialize *init = reinterpret_cast<Initialize *>(instr); + const Initialize *init = reinterpret_cast<const Initialize *>(instr); ip += init->size(); - bool ok = true; - dataModel->evaluateInitialization(init->expression, &ok); - return ok; + dataModel->evaluateInitialization(init->expression, ok); + return ip; } case Instruction::DoneData: { qCDebug(qscxmlLog) << stateMachine << "Executing DoneData step"; - DoneData *doneData = reinterpret_cast<DoneData *>(instr); + const DoneData *doneData = reinterpret_cast<const DoneData *>(instr); QString eventName = QStringLiteral("done.state.") + extraData.toString(); QScxmlEventBuilder event(stateMachine, eventName, doneData); + auto e = event(); + e->setEventType(QScxmlEvent::InternalEvent); qCDebug(qscxmlLog) << stateMachine << "submitting event" << eventName; - stateMachine->submitEvent(event()); - return true; + stateMachine->submitEvent(e); + return ip; } default: Q_UNREACHABLE(); - return false; + *ok = false; + return ip; } } #endif // BUILD_QSCXMLC -Builder::Builder() - : m_initialSetup(QScxmlExecutableContent::NoInstruction) - , m_isCppDataModel(false) -{ - m_activeSequences.reserve(4); -} - -bool Builder::visit(DocumentModel::Send *node) -{ - auto instr = m_instructions.add<Send>(Send::calculateExtraSize(node->params.size(), node->namelist.size())); - instr->instructionLocation = createContext(QStringLiteral("send")); - instr->event = addString(node->event); - instr->eventexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("eventexpr"), node->eventexpr); - instr->type = addString(node->type); - instr->typeexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("typeexpr"), node->typeexpr); - instr->target = addString(node->target); - instr->targetexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("targetexpr"), node->targetexpr); - instr->id = addString(node->id); - instr->idLocation = addString(node->idLocation); - instr->delay = addString(node->delay); - instr->delayexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("delayexpr"), node->delayexpr); - instr->content = addString(node->content); - generate(&instr->namelist, node->namelist); - generate(instr->params(), node->params); - return false; -} - -void Builder::visit(DocumentModel::Raise *node) -{ - auto instr = m_instructions.add<Raise>(); - instr->event = addString(node->event); -} - -void Builder::visit(DocumentModel::Log *node) -{ - auto instr = m_instructions.add<Log>(); - instr->label = addString(node->label); - instr->expr = createEvaluatorString(QStringLiteral("log"), QStringLiteral("expr"), node->expr); -} - -void Builder::visit(DocumentModel::Script *node) -{ - auto instr = m_instructions.add<JavaScript>(); - instr->go = createEvaluatorVoid(QStringLiteral("script"), QStringLiteral("source"), node->content); -} - -void Builder::visit(DocumentModel::Assign *node) -{ - auto instr = m_instructions.add<Assign>(); - auto ctxt = createContext(QStringLiteral("assign"), QStringLiteral("expr"), node->expr); - instr->expression = addAssignment(node->location, node->expr, ctxt); -} - -bool Builder::visit(DocumentModel::If *node) -{ - auto instr = m_instructions.add<If>(node->conditions.size()); - instr->conditions.count = node->conditions.size(); - auto it = instr->conditions.data(); - QString tag = QStringLiteral("if"); - for (int i = 0, ei = node->conditions.size(); i != ei; ++i) { - *it++ = createEvaluatorBool(tag, QStringLiteral("cond"), node->conditions.at(i)); - if (i == 0) { - tag = QStringLiteral("elif"); - } - } - auto outSequences = m_instructions.add<InstructionSequences>(); - generate(outSequences, node->blocks); - return false; -} - -bool Builder::visit(DocumentModel::Foreach *node) -{ - auto instr = m_instructions.add<Foreach>(); - auto ctxt = createContextString(QStringLiteral("foreach")); - instr->doIt = addForeach(node->array, node->item, node->index, ctxt); - startSequence(&instr->block); - visit(&node->block); - endSequence(); - return false; -} - -void Builder::visit(DocumentModel::Cancel *node) -{ - auto instr = m_instructions.add<Cancel>(); - instr->sendid = addString(node->sendid); - instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel"), QStringLiteral("sendidexpr"), node->sendidexpr); -} - -ContainerId Builder::generate(const DocumentModel::DoneData *node) -{ - auto id = m_instructions.newContainerId(); - DoneData *doneData; - if (node) { - doneData = m_instructions.add<DoneData>(node->params.size() * Param::calculateSize()); - doneData->contents = addString(node->contents); - doneData->expr = createEvaluatorString(QStringLiteral("donedata"), QStringLiteral("expr"), node->expr); - generate(&doneData->params, node->params); - } else { - doneData = m_instructions.add<DoneData>(); - doneData->contents = NoString; - doneData->expr = NoEvaluator; - doneData->params.count = 0; - } - doneData->location = createContext(QStringLiteral("final")); - return id; -} - -StringId Builder::createContext(const QString &instrName) -{ - return addString(createContextString(instrName)); -} - -void Builder::generate(const QVector<DocumentModel::DataElement *> &dataElements) -{ - foreach (DocumentModel::DataElement *el, dataElements) { - auto ctxt = createContext(QStringLiteral("data"), QStringLiteral("expr"), el->expr); - auto evaluator = addDataElement(el->id, el->expr, ctxt); - if (evaluator != NoEvaluator) { - auto instr = m_instructions.add<QScxmlExecutableContent::Initialize>(); - instr->expression = evaluator; - } - } -} - -ContainerId Builder::generate(const DocumentModel::InstructionSequences &inSequences) -{ - if (inSequences.isEmpty()) - return NoInstruction; - - auto id = m_instructions.newContainerId(); - auto outSequences = m_instructions.add<InstructionSequences>(); - generate(outSequences, inSequences); - return id; -} - -void Builder::generate(Array<Param> *out, const QVector<DocumentModel::Param *> &in) -{ - out->count = in.size(); - Param *it = out->data(); - foreach (DocumentModel::Param *f, in) { - it->name = addString(f->name); - it->expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), f->expr); - it->location = addString(f->location); - ++it; - } -} - -void Builder::generate(InstructionSequences *outSequences, const DocumentModel::InstructionSequences &inSequences) -{ - int sequencesOffset = m_instructions.offset(outSequences); - int sequenceCount = 0; - int entryCount = 0; - foreach (DocumentModel::InstructionSequence *sequence, inSequences) { - ++sequenceCount; - startNewSequence(); - visit(sequence); - entryCount += endSequence()->size(); - } - outSequences = m_instructions.at<InstructionSequences>(sequencesOffset); - outSequences->sequenceCount = sequenceCount; - outSequences->entryCount = entryCount; -} - -void Builder::generate(Array<StringId> *out, const QStringList &in) -{ - out->count = in.size(); - StringId *it = out->data(); - foreach (const QString &str, in) { - *it++ = addString(str); - } -} - -ContainerId Builder::startNewSequence() -{ - auto id = m_instructions.newContainerId(); - auto sequence = m_instructions.add<InstructionSequence>(); - startSequence(sequence); - return id; -} - -void Builder::startSequence(InstructionSequence *sequence) -{ - SequenceInfo info; - info.location = m_instructions.offset(sequence); - info.entryCount = 0; - m_activeSequences.push_back(info); - m_instructions.setSequenceInfo(&m_activeSequences.last()); - sequence->instructionType = Instruction::Sequence; - sequence->entryCount = -1; // checked in endSequence -} - -InstructionSequence *Builder::endSequence() -{ - SequenceInfo info = m_activeSequences.back(); - m_activeSequences.pop_back(); - m_instructions.setSequenceInfo(m_activeSequences.isEmpty() ? Q_NULLPTR : &m_activeSequences.last()); - - auto sequence = m_instructions.at<InstructionSequence>(info.location); - Q_ASSERT(sequence->entryCount == -1); // set in startSequence - sequence->entryCount = info.entryCount; - if (!m_activeSequences.isEmpty()) - m_activeSequences.last().entryCount += info.entryCount; - return sequence; -} - -EvaluatorId Builder::createEvaluatorString(const QString &instrName, const QString &attrName, const QString &expr) -{ - if (!expr.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_stringEvaluators.insert(id, expr); - return id; - } else { - QString loc = createContext(instrName, attrName, expr); - return addEvaluator(expr, loc); - } - } - - return NoEvaluator; -} - -EvaluatorId Builder::createEvaluatorBool(const QString &instrName, const QString &attrName, const QString &cond) -{ - if (!cond.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_boolEvaluators.insert(id, cond); - return id; - } else { - QString loc = createContext(instrName, attrName, cond); - return addEvaluator(cond, loc); - } - } - - return NoEvaluator; -} - -EvaluatorId Builder::createEvaluatorVariant(const QString &instrName, const QString &attrName, const QString &expr) -{ - if (!expr.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_variantEvaluators.insert(id, expr); - return id; - } else { - QString loc = createContext(instrName, attrName, expr); - return addEvaluator(expr, loc); - } - } - - return NoEvaluator; -} - -EvaluatorId Builder::createEvaluatorVoid(const QString &instrName, const QString &attrName, const QString &stuff) -{ - if (!stuff.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_voidEvaluators.insert(id, stuff); - return id; - } else { - QString loc = createContext(instrName, attrName, stuff); - return addEvaluator(stuff, loc); - } - } - - return NoEvaluator; -} - -DynamicTableData *Builder::tableData() -{ - auto td = new DynamicTableData; - td->strings = m_stringTable.data(); - td->theInstructions = m_instructions.data(); - td->theEvaluators = m_evaluators.data(); - td->theAssignments = m_assignments.data(); - td->theForeaches = m_foreaches.data(); - td->theDataNameIds = m_dataIds; - td->theInitialSetup = m_initialSetup; - td->theName = m_name; - return td; -} - -QString DynamicTableData::string(StringId id) const -{ - return id == NoString ? QString() : strings.at(id); -} - -Instructions DynamicTableData::instructions() const -{ - return const_cast<Instructions>(theInstructions.data()); -} - -EvaluatorInfo DynamicTableData::evaluatorInfo(EvaluatorId evaluatorId) const -{ - return theEvaluators[evaluatorId]; -} - -AssignmentInfo DynamicTableData::assignmentInfo(EvaluatorId assignmentId) const -{ - return theAssignments[assignmentId]; -} - -ForeachInfo DynamicTableData::foreachInfo(EvaluatorId foreachId) const -{ - return theForeaches[foreachId]; -} - -StringId *DynamicTableData::dataNames(int *count) const -{ - Q_ASSERT(count); - *count = theDataNameIds.size(); - return const_cast<StringId *>(theDataNameIds.data()); -} - -ContainerId DynamicTableData::initialSetup() const -{ - return theInitialSetup; -} - -QString DynamicTableData::name() const -{ - return theName; -} - -QVector<qint32> DynamicTableData::instructionTable() const -{ - return theInstructions; -} - -QVector<QString> DynamicTableData::stringTable() const -{ - return strings; -} - -QVector<EvaluatorInfo> DynamicTableData::evaluators() const -{ - return theEvaluators; -} - -QVector<AssignmentInfo> DynamicTableData::assignments() const -{ - return theAssignments; -} - -QVector<ForeachInfo> DynamicTableData::foreaches() const -{ - return theForeaches; -} - -StringIds DynamicTableData::allDataNameIds() const -{ - return theDataNameIds; -} - QT_END_NAMESPACE diff --git a/src/scxml/qscxmlexecutablecontent.h b/src/scxml/qscxmlexecutablecontent.h index 4bcfc36..cdf6b2b 100644 --- a/src/scxml/qscxmlexecutablecontent.h +++ b/src/scxml/qscxmlexecutablecontent.h @@ -42,20 +42,18 @@ #include <QtScxml/qscxmlglobals.h> -#include <QVector> - QT_BEGIN_NAMESPACE namespace QScxmlExecutableContent { -typedef int ContainerId; -enum { NoInstruction = -1 }; +typedef qint32 ContainerId; +enum { NoContainer = -1 }; typedef qint32 StringId; -typedef QVector<StringId> StringIds; enum { NoString = -1 }; -typedef qint32 *Instructions; - -class QScxmlExecutionEngine; +typedef qint32 InstructionId; +enum { NoInstruction = -1 }; +typedef qint32 EvaluatorId; +enum { NoEvaluator = -1 }; #if defined(Q_CC_MSVC) || defined(Q_CC_GNU) #pragma pack(push, 4) // 4 == sizeof(qint32) @@ -77,12 +75,26 @@ struct ForeachInfo { StringId index; StringId context; }; + +struct ParameterInfo { + StringId name; + EvaluatorId expr; + StringId location; +}; + +struct InvokeInfo { + StringId id; + StringId prefix; + StringId location; + StringId context; + EvaluatorId expr; + ContainerId finalize; + bool autoforward; +}; #if defined(Q_CC_MSVC) || defined(Q_CC_GNU) #pragma pack(pop) #endif -typedef qint32 EvaluatorId; -enum { NoEvaluator = -1 }; } // QScxmlExecutableContent namespace QT_END_NAMESPACE diff --git a/src/scxml/qscxmlexecutablecontent_p.h b/src/scxml/qscxmlexecutablecontent_p.h index 0d2e286..4429686 100644 --- a/src/scxml/qscxmlexecutablecontent_p.h +++ b/src/scxml/qscxmlexecutablecontent_p.h @@ -52,10 +52,9 @@ // #include <QtScxml/qscxmlexecutablecontent.h> -#include <QtScxml/qscxmltabledata.h> -#include <QtScxml/private/qscxmlparser_p.h> -#include <QtCore/qmap.h> -#include <QtCore/qvariant.h> +#include <QtScxml/private/qscxmltabledata_p.h> +#include <QtScxml/private/qscxmlcompiler_p.h> +#include <QTextStream> #ifndef BUILD_QSCXMLC #include <QtScxml/qscxmldatamodel.h> @@ -104,20 +103,11 @@ struct Array T *data() { return const_cast<T *>(const_data()); } const T *const_data() const { return reinterpret_cast<const T *>(reinterpret_cast<const char *>(this) + sizeof(Array<T>)); } - const T &at(int pos) { return *(data() + pos); } + const T &at(int pos) const { return *(const_data() + pos); } int dataSize() const { return count * sizeof(T) / sizeof(qint32); } int size() const { return sizeof(Array<T>) / sizeof(qint32) + dataSize(); } }; -struct Q_SCXML_EXPORT Param -{ - StringId name; - EvaluatorId expr; - StringId location; - - static int calculateSize() { return sizeof(Param) / sizeof(qint32); } -}; - struct Q_SCXML_EXPORT Instruction { enum InstructionType: qint32 { @@ -141,7 +131,7 @@ struct Q_SCXML_EXPORT DoneData: Instruction StringId location; StringId contents; EvaluatorId expr; - Array<Param> params; + Array<ParameterInfo> params; static InstructionType kind() { return Instruction::DoneData; } }; @@ -152,7 +142,11 @@ struct Q_SCXML_EXPORT InstructionSequence: Instruction // Instruction[] instructions; static InstructionType kind() { return Instruction::Sequence; } - Instructions instructions() { return reinterpret_cast<Instructions>(this) + sizeof(InstructionSequence) / sizeof(qint32); } + const InstructionId *instructions() const + { + return reinterpret_cast<const InstructionId *>(this) + + sizeof(InstructionSequence) / sizeof(qint32); + } int size() const { return sizeof(InstructionSequence) / sizeof(qint32) + entryCount; } }; @@ -163,14 +157,17 @@ struct Q_SCXML_EXPORT InstructionSequences: Instruction // InstructionSequence[] sequences; static InstructionType kind() { return Instruction::Sequences; } - InstructionSequence *sequences() { - return reinterpret_cast<InstructionSequence *>(reinterpret_cast<Instructions>(this) + sizeof(InstructionSequences) / sizeof(qint32)); + const InstructionSequence *sequences() const { + return reinterpret_cast<const InstructionSequence *>( + reinterpret_cast<const InstructionId *>(this) + + sizeof(InstructionSequences) / sizeof(qint32)); } int size() const { return sizeof(InstructionSequences)/sizeof(qint32) + entryCount; } - Instructions at(int pos) { - Instructions seq = reinterpret_cast<Instructions>(sequences()); + const InstructionId *at(int pos) const + { + const InstructionId *seq = reinterpret_cast<const InstructionId *>(sequences()); while (pos--) { - seq += reinterpret_cast<InstructionSequence *>(seq)->size(); + seq += reinterpret_cast<const InstructionSequence *>(seq)->size(); } return seq; } @@ -190,17 +187,35 @@ struct Q_SCXML_EXPORT Send: Instruction StringId delay; EvaluatorId delayexpr; StringId content; + EvaluatorId contentexpr; Array<StringId> namelist; // Array<Param> params; static InstructionType kind() { return Instruction::Send; } - int size() { return sizeof(Send) / sizeof(qint32) + namelist.dataSize() + params()->size(); } - Array<Param> *params() { - return reinterpret_cast<Array<Param> *>( - reinterpret_cast<Instructions>(this) + sizeof(Send) / sizeof(qint32) + namelist.dataSize()); + + int paramsOffset() const + { + return sizeof(Send) / sizeof(qint32) + namelist.dataSize(); } + + int size() const + { + return paramsOffset() + params()->size(); + } + + const Array<ParameterInfo> *params() const { + return reinterpret_cast<const Array<ParameterInfo> *>( + reinterpret_cast<const InstructionId *>(this) + paramsOffset()); + } + + Array<ParameterInfo> *params() { + return reinterpret_cast<Array<ParameterInfo> *>( + reinterpret_cast<InstructionId *>(this) + paramsOffset()); + } + static int calculateExtraSize(int paramCount, int nameCount) { - return 1 + paramCount * sizeof(Param) / sizeof(qint32) + nameCount * sizeof(StringId) / sizeof(qint32); + return 1 + paramCount * sizeof(ParameterInfo) / sizeof(qint32) + + nameCount * sizeof(StringId) / sizeof(qint32); } }; @@ -249,13 +264,17 @@ struct Q_SCXML_EXPORT If: Instruction { Array<EvaluatorId> conditions; // InstructionSequences blocks; - InstructionSequences *blocks() { - return reinterpret_cast<InstructionSequences *>( - reinterpret_cast<Instructions>(this) + sizeof(If) / sizeof(qint32) + conditions.dataSize()); + const InstructionSequences *blocks() const { + return reinterpret_cast<const InstructionSequences *>( + reinterpret_cast<const InstructionId *>(this) + sizeof(If) / sizeof(qint32) + + conditions.dataSize()); } static InstructionType kind() { return Instruction::If; } - int size() { return sizeof(If) / sizeof(qint32) + blocks()->size() + conditions.dataSize(); } + int size() const + { + return sizeof(If) / sizeof(qint32) + blocks()->size() + conditions.dataSize(); + } }; struct Q_SCXML_EXPORT Foreach: Instruction @@ -265,7 +284,10 @@ struct Q_SCXML_EXPORT Foreach: Instruction static InstructionType kind() { return Instruction::Foreach; } int size() const { return sizeof(Foreach) / sizeof(qint32) + block.entryCount; } - Instructions blockstart() { return reinterpret_cast<Instructions>(&block); } + const InstructionId *blockstart() const + { + return reinterpret_cast<const InstructionId *>(&block); + } }; struct Q_SCXML_EXPORT Cancel: Instruction @@ -277,244 +299,197 @@ struct Q_SCXML_EXPORT Cancel: Instruction int size() const { return sizeof(Cancel) / sizeof(qint32); } }; -#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) -#pragma pack(pop) -#endif - -class Q_SCXML_EXPORT DynamicTableData: -#ifndef BUILD_QSCXMLC - public QObject, -#endif // BUILD_QSCXMLC - public QScxmlTableData -{ -#ifndef BUILD_QSCXMLC - Q_OBJECT -#endif - -public: - QString string(StringId id) const Q_DECL_OVERRIDE; - Instructions instructions() const Q_DECL_OVERRIDE; - EvaluatorInfo evaluatorInfo(EvaluatorId evaluatorId) const Q_DECL_OVERRIDE; - AssignmentInfo assignmentInfo(EvaluatorId assignmentId) const Q_DECL_OVERRIDE; - ForeachInfo foreachInfo(EvaluatorId foreachId) const Q_DECL_OVERRIDE; - StringId *dataNames(int *count) const Q_DECL_OVERRIDE; - ContainerId initialSetup() const Q_DECL_OVERRIDE; - QString name() const Q_DECL_OVERRIDE; - - QVector<qint32> instructionTable() const; - QVector<QString> stringTable() const; - QVector<EvaluatorInfo> evaluators() const; - QVector<AssignmentInfo> assignments() const; - QVector<ForeachInfo> foreaches() const; - StringIds allDataNameIds() const; - -private: - friend class Builder; - QVector<QString> strings; - QVector<qint32> theInstructions; - QVector<EvaluatorInfo> theEvaluators; - QVector<AssignmentInfo> theAssignments; - QVector<ForeachInfo> theForeaches; - StringIds theDataNameIds; - EvaluatorId theInitialSetup; - QString theName; -}; - -class Q_SCXML_EXPORT Builder: public DocumentModel::NodeVisitor -{ -public: - Builder(); - -protected: // visitor - using NodeVisitor::visit; - - bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Raise *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Log *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Script *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Assign *node) Q_DECL_OVERRIDE; - bool visit(DocumentModel::If *node) Q_DECL_OVERRIDE; - bool visit(DocumentModel::Foreach *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Cancel *node) Q_DECL_OVERRIDE; - -protected: - ContainerId generate(const DocumentModel::DoneData *node); - StringId createContext(const QString &instrName); - void generate(const QVector<DocumentModel::DataElement *> &dataElements); - ContainerId generate(const DocumentModel::InstructionSequences &inSequences); - void generate(Array<Param> *out, const QVector<DocumentModel::Param *> &in); - void generate(InstructionSequences *outSequences, const DocumentModel::InstructionSequences &inSequences); - void generate(Array<StringId> *out, const QStringList &in); - ContainerId startNewSequence(); - void startSequence(InstructionSequence *sequence); - InstructionSequence *endSequence(); - EvaluatorId createEvaluatorString(const QString &instrName, const QString &attrName, const QString &expr); - EvaluatorId createEvaluatorBool(const QString &instrName, const QString &attrName, const QString &cond); - EvaluatorId createEvaluatorVariant(const QString &instrName, const QString &attrName, const QString &expr); - EvaluatorId createEvaluatorVoid(const QString &instrName, const QString &attrName, const QString &stuff); - - virtual QString createContextString(const QString &instrName) const = 0; - virtual QString createContext(const QString &instrName, const QString &attrName, const QString &attrValue) const = 0; - - DynamicTableData *tableData(); - - StringId addString(const QString &str) - { return str.isEmpty() ? NoString : m_stringTable.add(str); } - - void setInitialSetup(ContainerId id) - { m_initialSetup = id; } +struct StateTable { + int version; + int name; + enum: int { + InvalidDataModel = -1, + NullDataModel = 0, + EcmaScriptDataModel = 1, + CppDataModel = 2 + } dataModel; + int childStates; // offset into offsets + int initialTransition; + int initialSetup; + enum: int { InvalidBinding = -1, EarlyBinding = 0, LateBinding = 1 } binding; + int maxServiceId; + int stateOffset, stateCount; + int transitionOffset, transitionCount; + int arrayOffset, arraySize; + + enum { terminator = 0xc0ff33 }; + enum { InvalidIndex = -1 }; + + struct State { + int name; + int parent; + enum: int { + Invalid = -1, + Normal = 0, + Parallel = 1, + Final = 2, + ShallowHistory = 3, + DeepHistory = 4 + } type; + int initialTransition; + int initInstructions; + int entryInstructions; + int exitInstructions; + int doneData; + int childStates; // offset into arrays + int transitions; // offset into arrays + int serviceFactoryIds; // offset into arrays + + State() + : name(InvalidIndex) + , parent(InvalidIndex) + , type(Invalid) + , initialTransition(InvalidIndex) + , initInstructions(InvalidIndex) + , entryInstructions(InvalidIndex) + , exitInstructions(InvalidIndex) + , doneData(InvalidIndex) + , childStates(InvalidIndex) + , transitions(InvalidIndex) + , serviceFactoryIds(InvalidIndex) + {} - void setName(const QString &name) - { m_name = name; } + bool isAtomic() const + { return childStates == InvalidIndex; } - bool isCppDataModel() const - { return m_isCppDataModel; } + bool isCompound() const + { return type == Normal && childStates != InvalidIndex; } - void setIsCppDataModel(bool onoff) - { m_isCppDataModel = onoff; } + bool parentIsScxmlElement() const + { return parent == InvalidIndex; } - QMap<EvaluatorId, QString> boolEvaluators() const - { return m_boolEvaluators; } + bool isHistoryState() const + { return type == ShallowHistory || type == DeepHistory; } - QMap<EvaluatorId, QString> stringEvaluators() const - { return m_stringEvaluators; } + bool isParallel() const + { return type == Parallel; } + }; - QMap<EvaluatorId, QString> variantEvaluators() const - { return m_variantEvaluators; } + struct Transition { + int events; // offset into offsets + int condition; + enum: int { + Invalid = -1, + Internal = 0, + External = 1, + Synthetic = 2 + } type; + int source; + int targets; // offset into offsets + int transitionInstructions; + + Transition() + : events(InvalidIndex) + , condition(InvalidIndex) + , type(Invalid) + , source(InvalidIndex) + , targets(InvalidIndex) + , transitionInstructions(InvalidIndex) + {} + }; - QMap<EvaluatorId, QString> voidEvaluators() const - { return m_voidEvaluators; } + struct Array { + Array(const int *start): start(start) {} + int size() const { return *start; } + bool isValid() const { return start != nullptr; } -private: - template <typename T, typename U> - class Table { - QVector<T> elements; - QMap<T, int> indexForElement; - - public: - U add(const T &s, bool uniqueOnly = true) { - int pos = uniqueOnly ? indexForElement.value(s, -1) : -1; - if (pos == -1) { - pos = elements.size(); - elements.append(s); - indexForElement.insert(s, pos); - } - return pos; + int operator[](int idx) const { + Q_ASSERT(idx >= 0); + Q_ASSERT(idx < size()); + return *(start + idx + 1); } - QVector<T> data() { - elements.squeeze(); - return elements; - } - }; - Table<QString, StringId> m_stringTable; - - struct SequenceInfo { - int location; - qint32 entryCount; // the amount of qint32's that the instructions take up - }; - QVector<SequenceInfo> m_activeSequences; + struct const_iterator: public std::iterator<std::forward_iterator_tag, int, ptrdiff_t, + const int *, const int &> + { + const_iterator(const Array &a, int pos): a(a), pos(pos) {} - class InstructionStorage { - public: - InstructionStorage() - : m_info(Q_NULLPTR) - {} + const_iterator &operator++() { + if (pos < a.size()) ++pos; + return *this; + } - ContainerId newContainerId() const { return m_instr.size(); } + bool operator==(const const_iterator &other) const + { return &other.a == &a && other.pos == pos; } - template <typename T> - T *add(int extra = 0) - { - int pos = m_instr.size(); - int size = sizeof(T) / sizeof(qint32) + extra; - if (m_info) - m_info->entryCount += size; - m_instr.resize(pos + size); - T *instr = at<T>(pos); - Q_ASSERT(instr->instructionType == 0); - instr->instructionType = T::kind(); - return instr; - } + bool operator!=(const StateTable::Array::const_iterator &other) + { return !this->operator==(other); } - int offset(Instruction *instr) const - { - return reinterpret_cast<qint32 *>(instr) - m_instr.data(); - } + int operator*() const { + if (pos < a.size()) + return a[pos]; + else + return -1; + } - template <typename T> - T *at(int offset) - { - return reinterpret_cast<T *>(&m_instr[offset]); - } + private: + const Array &a; + int pos; + }; - QVector<qint32> data() - { - m_instr.squeeze(); - return m_instr; - } + const_iterator begin() const + { return const_iterator(*this, 0); } - void setSequenceInfo(SequenceInfo *info) - { - m_info = info; - } + const_iterator end() const + { return const_iterator(*this, size()); } private: - QVector<qint32> m_instr; - SequenceInfo *m_info; + const int *start; }; - InstructionStorage m_instructions; - EvaluatorId addEvaluator(const QString &expr, const QString &context) + StateTable() + : version(InvalidIndex) + , name(InvalidIndex) + , dataModel(InvalidDataModel) + , childStates(InvalidIndex) + , initialTransition(InvalidIndex) + , initialSetup(InvalidIndex) + , binding(InvalidBinding) + , maxServiceId(InvalidIndex) + , stateOffset(InvalidIndex), stateCount(InvalidIndex) + , transitionOffset(InvalidIndex), transitionCount(InvalidIndex) + , arrayOffset(InvalidIndex), arraySize(InvalidIndex) + {} + + const State &state(int idx) const { - EvaluatorInfo ei; - ei.expr = addString(expr); - ei.context = addString(context); - return m_evaluators.add(ei); + Q_ASSERT(idx >= 0); + Q_ASSERT(idx < stateCount); + return reinterpret_cast<const State *>( + reinterpret_cast<const int *>(this) + stateOffset)[idx]; } - EvaluatorId addAssignment(const QString &dest, const QString &expr, const QString &context) + const Transition &transition(int idx) const { - AssignmentInfo ai; - ai.dest = addString(dest); - ai.expr = addString(expr); - ai.context = addString(context); - return m_assignments.add(ai); + Q_ASSERT(idx >= 0); + Q_ASSERT(idx < transitionCount); + return reinterpret_cast<const Transition *>( + reinterpret_cast<const int *>(this) + transitionOffset)[idx]; } - EvaluatorId addForeach(const QString &array, const QString &item, const QString &index, const QString &context) + const Array array(int idx) const { - ForeachInfo fi; - fi.array = addString(array); - fi.item = addString(item); - fi.index = addString(index); - fi.context = addString(context); - return m_foreaches.add(fi); + Q_ASSERT(idx < arraySize); + if (idx >= 0) { + const int *start = reinterpret_cast<const int *>(this) + arrayOffset + idx; + Q_ASSERT(*start + idx < arraySize); + return Array(start); + } else { + return Array(nullptr); + } } +}; - EvaluatorId addDataElement(const QString &id, const QString &expr, const QString &context) - { - auto str = addString(id); - if (!m_dataIds.contains(str)) - m_dataIds.append(str); - if (expr.isEmpty()) - return NoEvaluator; - - return addAssignment(id, expr, context); - } +#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) +#pragma pack(pop) +#endif - Table<EvaluatorInfo, EvaluatorId> m_evaluators; - Table<AssignmentInfo, EvaluatorId> m_assignments; - Table<ForeachInfo, EvaluatorId> m_foreaches; - QMap<EvaluatorId, QString> m_boolEvaluators; - QMap<EvaluatorId, QString> m_stringEvaluators; - QMap<EvaluatorId, QString> m_variantEvaluators; - QMap<EvaluatorId, QString> m_voidEvaluators; - StringIds m_dataIds; - ContainerId m_initialSetup; - QString m_name; - bool m_isCppDataModel; -}; +} // QScxmlExecutableContent namespace class QScxmlExecutionEngine { @@ -523,17 +498,16 @@ class QScxmlExecutionEngine public: QScxmlExecutionEngine(QScxmlStateMachine *stateMachine); - bool execute(ContainerId ip, const QVariant &extraData = QVariant()); + bool execute(QScxmlExecutableContent::ContainerId ip, const QVariant &extraData = QVariant()); private: - bool step(Instructions &ip); + const QScxmlExecutableContent::InstructionId *step( + const QScxmlExecutableContent::InstructionId *ip, bool *ok); QScxmlStateMachine *stateMachine; QVariant extraData; }; -} // QScxmlExecutableContent namespace - QT_END_NAMESPACE #endif // EXECUTABLECONTENT_P_H diff --git a/src/scxml/qscxmlglobals.h b/src/scxml/qscxmlglobals.h index 9501ab6..d9bf7b5 100644 --- a/src/scxml/qscxmlglobals.h +++ b/src/scxml/qscxmlglobals.h @@ -39,7 +39,7 @@ #ifndef SCXMLGLOBALS_H #define SCXMLGLOBALS_H -#include <qglobal.h> +#include <QtCore/qglobal.h> QT_BEGIN_NAMESPACE diff --git a/src/scxml/qscxmlinvokableservice.cpp b/src/scxml/qscxmlinvokableservice.cpp index d473915..9af6be7 100644 --- a/src/scxml/qscxmlinvokableservice.cpp +++ b/src/scxml/qscxmlinvokableservice.cpp @@ -38,49 +38,41 @@ ****************************************************************************/ #include "qscxmlglobals_p.h" -#include "qscxmlinvokableservice.h" +#include "qscxmlinvokableservice_p.h" #include "qscxmlstatemachine_p.h" QT_BEGIN_NAMESPACE -class QScxmlInvokableService::Data +QScxmlInvokableServicePrivate::QScxmlInvokableServicePrivate(QScxmlStateMachine *parentStateMachine) + : parentStateMachine(parentStateMachine) { -public: - Data(QScxmlInvokableServiceFactory *service, QScxmlStateMachine *parent) - : service(service) - , parent(parent) - {} - - QScxmlInvokableServiceFactory *service; - QScxmlStateMachine *parent; -}; - -QScxmlInvokableService::QScxmlInvokableService(QScxmlInvokableServiceFactory *service, - QScxmlStateMachine *parent) - : d(new Data(service, parent)) -{ - qRegisterMetaType<QScxmlInvokableService *>(); + static int metaType = qRegisterMetaType<QScxmlInvokableService *>(); + Q_UNUSED(metaType); } -QScxmlInvokableService::~QScxmlInvokableService() -{ - delete d; -} +QScxmlInvokableServiceFactoryPrivate::QScxmlInvokableServiceFactoryPrivate( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &namelist, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters) + : invokeInfo(invokeInfo) + , names(namelist) + , parameters(parameters) +{} -bool QScxmlInvokableService::autoforward() const +QScxmlInvokableService::QScxmlInvokableService(QScxmlStateMachine *parentStateMachine, + QObject *parent) : + QObject(*(new QScxmlInvokableServicePrivate(parentStateMachine)), parent) { - return d->service->autoforward(); } QScxmlStateMachine *QScxmlInvokableService::parentStateMachine() const { - return d->parent; + Q_D(const QScxmlInvokableService); + return d->parentStateMachine; } -void QScxmlInvokableService::finalize() +void QScxmlInvokableService::finalize(QScxmlExecutableContent::ContainerId finalize) { - QScxmlExecutableContent::ContainerId finalize = d->service->finalizeContent(); - if (finalize != QScxmlExecutableContent::NoInstruction) { auto psm = parentStateMachine(); qCDebug(qscxmlLog) << psm << "running finalize on event"; @@ -89,45 +81,16 @@ void QScxmlInvokableService::finalize() } } -QScxmlInvokableServiceFactory *QScxmlInvokableService::service() const +QScxmlInvokableService::QScxmlInvokableService(QScxmlInvokableServicePrivate &dd, QObject *parent) : + QObject(dd, parent) { - return d->service; } -class QScxmlInvokableServiceFactory::Data -{ -public: - Data(QScxmlExecutableContent::StringId invokeLocation, QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, bool autoforward, - const QVector<QScxmlInvokableServiceFactory::Param> ¶ms, - QScxmlExecutableContent::ContainerId finalize) - : invokeLocation(invokeLocation) - , id(id) - , idPrefix(idPrefix) - , idlocation(idlocation) - , namelist(namelist) - , params(params) - , finalize(finalize) - , autoforward(autoforward) - {} - - QScxmlExecutableContent::StringId invokeLocation; - QScxmlExecutableContent::StringId id; - QScxmlExecutableContent::StringId idPrefix; - QScxmlExecutableContent::StringId idlocation; - QVector<QScxmlExecutableContent::StringId> namelist; - QVector<QScxmlInvokableServiceFactory::Param> params; - QScxmlExecutableContent::ContainerId finalize; - bool autoforward; -}; - QScxmlInvokableServiceFactory::QScxmlInvokableServiceFactory( - QScxmlExecutableContent::StringId invokeLocation, QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, bool autoforward, - const QVector<Param> ¶ms, QScxmlExecutableContent::ContainerId finalize) - : d(new Data(invokeLocation, id, idPrefix, idlocation, namelist, autoforward, params, finalize)) + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters) + : d(new QScxmlInvokableServiceFactoryPrivate(invokeInfo, names, parameters)) {} QScxmlInvokableServiceFactory::~QScxmlInvokableServiceFactory() @@ -135,30 +98,70 @@ QScxmlInvokableServiceFactory::~QScxmlInvokableServiceFactory() delete d; } -QString QScxmlInvokableServiceFactory::calculateId(QScxmlStateMachine *parent, bool *ok) const +QScxmlExecutableContent::InvokeInfo QScxmlInvokableServiceFactory::invokeInfo() const +{ + return d->invokeInfo; +} + +QVector<QScxmlExecutableContent::ParameterInfo> QScxmlInvokableServiceFactory::parameters() const +{ + return d->parameters; +} + +QVector<QScxmlExecutableContent::StringId> QScxmlInvokableServiceFactory::names() const +{ + return d->names; +} + +QString QScxmlInvokableServiceFactoryPrivate::calculateSrcexpr(QScxmlStateMachine *parent, + bool *ok) const +{ + Q_ASSERT(ok); + *ok = true; + auto dataModel = parent->dataModel(); + + if (invokeInfo.expr != QScxmlExecutableContent::NoEvaluator) { + *ok = false; + auto v = dataModel->evaluateToString(invokeInfo.expr, ok); + if (!*ok) + return QString(); + return v; + } + + return QString(); +} + +QString QScxmlInvokableServicePrivate::calculateId( + QScxmlStateMachine *parent, const QScxmlExecutableContent::InvokeInfo &invokeInfo, + bool *ok) const { Q_ASSERT(ok); *ok = true; auto stateMachine = parent->tableData(); - if (d->id != QScxmlExecutableContent::NoString) { - return stateMachine->string(d->id); + if (invokeInfo.id != QScxmlExecutableContent::NoString) { + return stateMachine->string(invokeInfo.id); } - QString id = QScxmlStateMachine::generateSessionId(stateMachine->string(d->idPrefix)); + const QString newId = QScxmlStateMachinePrivate::generateSessionId( + stateMachine->string(invokeInfo.prefix)); - if (d->idlocation != QScxmlExecutableContent::NoString) { - auto idloc = stateMachine->string(d->idlocation); - auto ctxt = stateMachine->string(d->invokeLocation); - *ok = parent->dataModel()->setScxmlProperty(idloc, id, ctxt); + if (invokeInfo.location != QScxmlExecutableContent::NoString) { + auto idloc = stateMachine->string(invokeInfo.location); + auto ctxt = stateMachine->string(invokeInfo.context); + *ok = parent->dataModel()->setScxmlProperty(idloc, newId, ctxt); if (!*ok) return QString(); } - return id; + return newId; } -QVariantMap QScxmlInvokableServiceFactory::calculateData(QScxmlStateMachine *parent, bool *ok) const +QVariantMap QScxmlInvokableServicePrivate::calculateData( + QScxmlStateMachine *parent, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters, + const QVector<QScxmlExecutableContent::StringId> &names, + bool *ok) const { Q_ASSERT(ok); @@ -166,7 +169,7 @@ QVariantMap QScxmlInvokableServiceFactory::calculateData(QScxmlStateMachine *par auto dataModel = parent->dataModel(); auto tableData = parent->tableData(); - foreach (const Param ¶m, d->params) { + for (const QScxmlExecutableContent::ParameterInfo ¶m : parameters) { auto name = tableData->string(param.name); if (param.expr != QScxmlExecutableContent::NoEvaluator) { @@ -192,7 +195,7 @@ QVariantMap QScxmlInvokableServiceFactory::calculateData(QScxmlStateMachine *par } } - foreach (QScxmlExecutableContent::StringId locid, d->namelist) { + for (QScxmlExecutableContent::StringId locid : names) { QString loc; if (locid != QScxmlExecutableContent::NoString) { loc = tableData->string(locid); @@ -214,91 +217,106 @@ QVariantMap QScxmlInvokableServiceFactory::calculateData(QScxmlStateMachine *par return result; } -bool QScxmlInvokableServiceFactory::autoforward() const -{ - return d->autoforward; -} - -QScxmlExecutableContent::ContainerId QScxmlInvokableServiceFactory::finalizeContent() const -{ - return d->finalize; -} +QScxmlScxmlServicePrivate::QScxmlScxmlServicePrivate(QScxmlStateMachine *stateMachine, + QScxmlStateMachine *parentStateMachine) : + QScxmlInvokableServicePrivate(parentStateMachine), stateMachine(stateMachine) +{} -QScxmlInvokableScxml::QScxmlInvokableScxml(QScxmlInvokableServiceFactory *service, - QScxmlStateMachine *stateMachine, - QScxmlStateMachine *parent) - : QScxmlInvokableService(service, parent) - , m_stateMachine(stateMachine) +QScxmlScxmlServicePrivate::~QScxmlScxmlServicePrivate() { - QScxmlStateMachinePrivate::get(stateMachine)->m_parentStateMachine = parent; + delete stateMachine; } -QScxmlInvokableScxml::~QScxmlInvokableScxml() +QScxmlScxmlService::QScxmlScxmlService(QScxmlStateMachine *stateMachine, + QScxmlStateMachine *parentStateMachine, + QObject *parent) + : QScxmlInvokableService(*(new QScxmlScxmlServicePrivate(stateMachine, parentStateMachine)), + parent) { - delete m_stateMachine; + QScxmlStateMachinePrivate::get(stateMachine)->m_parentStateMachine = parentStateMachine; } -bool QScxmlInvokableScxml::start() +bool QScxmlScxmlService::start(const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters, + const QVector<QScxmlExecutableContent::StringId> &names) { - qCDebug(qscxmlLog) << parentStateMachine() << "preparing to start" << m_stateMachine; + Q_D(QScxmlScxmlService); + qCDebug(qscxmlLog) << parentStateMachine() << "preparing to start" << d->stateMachine; bool ok = false; - auto id = service()->calculateId(parentStateMachine(), &ok); + auto id = d->calculateId(parentStateMachine(), invokeInfo, &ok); if (!ok) return false; - auto data = service()->calculateData(parentStateMachine(), &ok); + auto data = d->calculateData(parentStateMachine(), parameters, names, &ok); if (!ok) return false; - m_stateMachine->setSessionId(id); - m_stateMachine->setInitialValues(data); - if (m_stateMachine->init()) { - qCDebug(qscxmlLog) << parentStateMachine() << "starting" << m_stateMachine; - m_stateMachine->start(); + QScxmlStateMachinePrivate::get(d->stateMachine)->m_sessionId = id; + d->stateMachine->setInitialValues(data); + if (d->stateMachine->init()) { + qCDebug(qscxmlLog) << parentStateMachine() << "starting" << d->stateMachine; + d->stateMachine->start(); return true; } - qCDebug(qscxmlLog) << parentStateMachine() << "failed to start" << m_stateMachine; + qCDebug(qscxmlLog) << parentStateMachine() << "failed to start" << d->stateMachine; return false; } -QString QScxmlInvokableScxml::id() const +QString QScxmlScxmlService::id() const { - return m_stateMachine->sessionId(); + Q_D(const QScxmlScxmlService); + return d->stateMachine->sessionId(); } -QString QScxmlInvokableScxml::name() const +QString QScxmlScxmlService::name() const { - return m_stateMachine->name(); + Q_D(const QScxmlScxmlService); + return d->stateMachine->name(); } -void QScxmlInvokableScxml::postEvent(QScxmlEvent *event) +void QScxmlScxmlService::postEvent(QScxmlEvent *event) { - QScxmlStateMachinePrivate::get(m_stateMachine)->postEvent(event); + Q_D(QScxmlScxmlService); + QScxmlStateMachinePrivate::get(d->stateMachine)->postEvent(event); } -QScxmlStateMachine *QScxmlInvokableScxml::stateMachine() const +QScxmlStateMachine *QScxmlScxmlService::stateMachine() const { - return m_stateMachine; + Q_D(const QScxmlScxmlService); + return d->stateMachine; } -QScxmlInvokableScxmlServiceFactory::QScxmlInvokableScxmlServiceFactory( - QScxmlExecutableContent::StringId invokeLocation, - QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, - QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, - bool doAutoforward, - const QVector<QScxmlInvokableServiceFactory::Param> ¶ms, - QScxmlExecutableContent::ContainerId finalize) - : QScxmlInvokableServiceFactory(invokeLocation, id, idPrefix, idlocation, namelist, - doAutoforward, params, finalize) +QScxmlScxmlServiceFactory::QScxmlScxmlServiceFactory( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters) + : QScxmlInvokableServiceFactory(invokeInfo, names, parameters) +{} + +QScxmlScxmlService *QScxmlScxmlServiceFactory::invokeStatic(QScxmlStateMachine *childStateMachine, + QScxmlStateMachine *parentStateMachine) +{ + QScxmlStateMachinePrivate::get(childStateMachine)->setIsInvoked(true); + return new QScxmlScxmlService(childStateMachine, parentStateMachine); +} + +QScxmlDynamicScxmlServiceFactory::QScxmlDynamicScxmlServiceFactory( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters) + : QScxmlScxmlServiceFactory(invokeInfo, names, parameters) {} -QScxmlInvokableService *QScxmlInvokableScxmlServiceFactory::finishInvoke(QScxmlStateMachine *child, QScxmlStateMachine *parent) +QScxmlInvokableService *QScxmlDynamicScxmlServiceFactory::invoke( + QScxmlStateMachine *parentStateMachine) { - QScxmlStateMachinePrivate::get(child)->setIsInvoked(true); - return new QScxmlInvokableScxml(this, child, parent); + bool ok = true; + auto srcexpr = d->calculateSrcexpr(parentStateMachine, &ok); + if (!ok) + return Q_NULLPTR; + + return invokeDynamic(parentStateMachine, srcexpr); } QT_END_NAMESPACE diff --git a/src/scxml/qscxmlinvokableservice.h b/src/scxml/qscxmlinvokableservice.h index 21ba707..6efc07d 100644 --- a/src/scxml/qscxmlinvokableservice.h +++ b/src/scxml/qscxmlinvokableservice.h @@ -40,121 +40,122 @@ #ifndef QSCXMLINVOKABLESERVICE_H #define QSCXMLINVOKABLESERVICE_H -#include <QtScxml/qscxmldatamodel.h> - -#include <QString> -#include <QVariantMap> +#include "qscxmldatamodel.h" +#include <QtCore/qstring.h> QT_BEGIN_NAMESPACE class QScxmlEvent; class QScxmlStateMachine; -class QScxmlInvokableServiceFactory; -class Q_SCXML_EXPORT QScxmlInvokableService +class QScxmlInvokableServicePrivate; +class Q_SCXML_EXPORT QScxmlInvokableService : public QObject { + Q_OBJECT + Q_DECLARE_PRIVATE(QScxmlInvokableService) + Q_PROPERTY(QScxmlStateMachine *parentStateMachine READ parentStateMachine CONSTANT) + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + public: - QScxmlInvokableService(QScxmlInvokableServiceFactory *service, QScxmlStateMachine *parentStateMachine); - virtual ~QScxmlInvokableService(); + QScxmlInvokableService(QScxmlStateMachine *parentStateMachine, + QObject *parent = nullptr); - bool autoforward() const; QScxmlStateMachine *parentStateMachine() const; - virtual bool start() = 0; + virtual bool start(const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters, + const QVector<QScxmlExecutableContent::StringId> &names) = 0; virtual QString id() const = 0; virtual QString name() const = 0; virtual void postEvent(QScxmlEvent *event) = 0; - void finalize(); + void finalize(QScxmlExecutableContent::ContainerId finalize); protected: - QScxmlInvokableServiceFactory *service() const; - -private: - class Data; - Data *d; + QScxmlInvokableService(QScxmlInvokableServicePrivate &dd, QObject *parent = nullptr); }; +class QScxmlInvokableServiceFactoryPrivate; class Q_SCXML_EXPORT QScxmlInvokableServiceFactory { public: - struct Q_SCXML_EXPORT Param - { - QScxmlExecutableContent::StringId name; - QScxmlExecutableContent::EvaluatorId expr; - QScxmlExecutableContent::StringId location; - - Param(QScxmlExecutableContent::StringId theName, - QScxmlExecutableContent::EvaluatorId theExpr, - QScxmlExecutableContent::StringId theLocation) - : name(theName) - , expr(theExpr) - , location(theLocation) - {} - - Param() - : name(QScxmlExecutableContent::NoString) - , expr(QScxmlExecutableContent::NoInstruction) - , location(QScxmlExecutableContent::NoString) - {} - }; - - QScxmlInvokableServiceFactory(QScxmlExecutableContent::StringId invokeLocation, - QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, - QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, - bool autoforward, - const QVector<Param> ¶ms, - QScxmlExecutableContent::ContainerId finalizeContent); + QScxmlInvokableServiceFactory( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters); virtual ~QScxmlInvokableServiceFactory(); - virtual QScxmlInvokableService *invoke(QScxmlStateMachine *parent) = 0; - -public: // callbacks from the service: - QString calculateId(QScxmlStateMachine *parent, bool *ok) const; - QVariantMap calculateData(QScxmlStateMachine *parent, bool *ok) const; - bool autoforward() const; - QScxmlExecutableContent::ContainerId finalizeContent() const; + virtual QScxmlInvokableService *invoke(QScxmlStateMachine *parentStateMachine) = 0; + QScxmlExecutableContent::InvokeInfo invokeInfo() const; + QVector<QScxmlExecutableContent::ParameterInfo> parameters() const; + QVector<QScxmlExecutableContent::StringId> names() const; -private: - class Data; - Data *d; +protected: + QScxmlInvokableServiceFactoryPrivate *d; }; -class Q_SCXML_EXPORT QScxmlInvokableScxml: public QScxmlInvokableService +class QScxmlScxmlServicePrivate; +class Q_SCXML_EXPORT QScxmlScxmlService: public QScxmlInvokableService { + Q_OBJECT + Q_DECLARE_PRIVATE(QScxmlScxmlService) + Q_PROPERTY(QScxmlStateMachine *stateMachine READ stateMachine CONSTANT) public: - QScxmlInvokableScxml(QScxmlInvokableServiceFactory *service, - QScxmlStateMachine *stateMachine, - QScxmlStateMachine *parentStateMachine); - virtual ~QScxmlInvokableScxml(); + QScxmlScxmlService(QScxmlStateMachine *stateMachine, + QScxmlStateMachine *parentStateMachine, + QObject *parent = nullptr); - bool start() Q_DECL_OVERRIDE; + bool start(const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters, + const QVector<QScxmlExecutableContent::StringId> &names) Q_DECL_OVERRIDE; QString id() const Q_DECL_OVERRIDE; QString name() const Q_DECL_OVERRIDE; void postEvent(QScxmlEvent *event) Q_DECL_OVERRIDE; QScxmlStateMachine *stateMachine() const; - -private: - QScxmlStateMachine *m_stateMachine; }; -class Q_SCXML_EXPORT QScxmlInvokableScxmlServiceFactory: public QScxmlInvokableServiceFactory +class Q_SCXML_EXPORT QScxmlScxmlServiceFactory: public QScxmlInvokableServiceFactory { public: - QScxmlInvokableScxmlServiceFactory(QScxmlExecutableContent::StringId invokeLocation, - QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, - QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, - bool doAutoforward, - const QVector<Param> ¶ms, - QScxmlExecutableContent::ContainerId finalize); + QScxmlScxmlServiceFactory(const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters); protected: - QScxmlInvokableService *finishInvoke(QScxmlStateMachine *child, QScxmlStateMachine *parent); + QScxmlScxmlService *invokeDynamic(QScxmlStateMachine *parentStateMachine, + const QString &sourceUrl); + QScxmlScxmlService *invokeStatic(QScxmlStateMachine *childStateMachine, + QScxmlStateMachine *parentStateMachine); +}; + +template<class T> +class QScxmlStaticScxmlServiceFactory: public QScxmlScxmlServiceFactory +{ +public: + QScxmlStaticScxmlServiceFactory( + const QScxmlExecutableContent::InvokeInfo &newInvokeInfo, + const QVector<QScxmlExecutableContent::StringId> &newNameList, + const QVector<QScxmlExecutableContent::ParameterInfo> &newParameters) + : QScxmlScxmlServiceFactory(newInvokeInfo, newNameList, newParameters) + {} + + QScxmlInvokableService *invoke(QScxmlStateMachine *parentStateMachine) Q_DECL_OVERRIDE + { + return invokeStatic(new T, parentStateMachine); + } +}; + +class Q_SCXML_EXPORT QScxmlDynamicScxmlServiceFactory: public QScxmlScxmlServiceFactory +{ +public: + QScxmlDynamicScxmlServiceFactory( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters); + + QScxmlInvokableService *invoke(QScxmlStateMachine *parentStateMachine) Q_DECL_OVERRIDE; }; QT_END_NAMESPACE diff --git a/src/scxml/qscxmlqstates_p.h b/src/scxml/qscxmlinvokableservice_p.h index 02b69b3..52bd6d0 100644 --- a/src/scxml/qscxmlqstates_p.h +++ b/src/scxml/qscxmlinvokableservice_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef SCXMLQSTATE_P_H -#define SCXMLQSTATE_P_H +#ifndef QSCXMLINVOKABLESERVICE_P_H +#define QSCXMLINVOKABLESERVICE_P_H // // W A R N I N G @@ -51,64 +51,51 @@ // We mean it. // -#include <QtScxml/qscxmlqstates.h> -#include <QtCore/private/qabstracttransition_p.h> -#include <QtCore/private/qstate_p.h> -#include <QtCore/private/qfinalstate_p.h> +#include "qscxmlinvokableservice.h" +#include <QtCore/private/qobject_p.h> QT_BEGIN_NAMESPACE -class QScxmlStatePrivate: public QStatePrivate +class QScxmlInvokableServicePrivate : public QObjectPrivate { - Q_DECLARE_PUBLIC(QScxmlState) - public: - static QScxmlStatePrivate *get(QScxmlState *s) { return s ? s->d_func() : nullptr; } + QScxmlInvokableServicePrivate(QScxmlStateMachine *parentStateMachine); - QScxmlStatePrivate(); - ~QScxmlStatePrivate(); + QString calculateId(QScxmlStateMachine *parent, + const QScxmlExecutableContent::InvokeInfo &invokeInfo, bool *ok) const; + QVariantMap calculateData(QScxmlStateMachine *parent, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters, + const QVector<QScxmlExecutableContent::StringId> &names, + bool *ok) const; - QScxmlExecutableContent::ContainerId initInstructions; - QScxmlExecutableContent::ContainerId onEntryInstructions; - QScxmlExecutableContent::ContainerId onExitInstructions; - QVector<QScxmlInvokableServiceFactory *> invokableServiceFactories; - QVector<QScxmlInvokableService *> invokedServices; - QVector<QScxmlInvokableService *> servicesWaitingToStart; + QScxmlStateMachine *parentStateMachine; }; -class QScxmlFinalStatePrivate: public QFinalStatePrivate +class QScxmlInvokableServiceFactoryPrivate { - Q_DECLARE_PUBLIC(QScxmlFinalState) - public: - static QScxmlFinalStatePrivate *get(QScxmlFinalState *s) { return s ? s->d_func() : nullptr; } + QScxmlInvokableServiceFactoryPrivate( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters); - QScxmlFinalStatePrivate(); - ~QScxmlFinalStatePrivate(); - - QScxmlExecutableContent::ContainerId doneData; - QScxmlExecutableContent::ContainerId onEntryInstructions; - QScxmlExecutableContent::ContainerId onExitInstructions; -}; + QString calculateSrcexpr(QScxmlStateMachine *parent, bool *ok) const; -class QScxmlBaseTransitionPrivate: public QAbstractTransitionPrivate -{ - Q_DECLARE_PUBLIC(QScxmlBaseTransition) - -public: - QStringList eventSelector; + QScxmlExecutableContent::InvokeInfo invokeInfo; + QVector<QScxmlExecutableContent::StringId> names; + QVector<QScxmlExecutableContent::ParameterInfo> parameters; }; -class QScxmlTransitionPrivate: public QScxmlBaseTransitionPrivate +class QScxmlScxmlServicePrivate : public QScxmlInvokableServicePrivate { public: - QScxmlTransitionPrivate(); - ~QScxmlTransitionPrivate(); + QScxmlScxmlServicePrivate(QScxmlStateMachine *stateMachine, + QScxmlStateMachine *parentStateMachine); + ~QScxmlScxmlServicePrivate(); - QScxmlExecutableContent::EvaluatorId conditionalExp; - QScxmlExecutableContent::ContainerId instructionsOnTransition; + QScxmlStateMachine *stateMachine; }; QT_END_NAMESPACE -#endif // SCXMLQSTATE_P_H +#endif // QSCXMLINVOKABLESERVICE_P_H diff --git a/src/scxml/qscxmlnulldatamodel.h b/src/scxml/qscxmlnulldatamodel.h index ee6f4ea..e4f7942 100644 --- a/src/scxml/qscxmlnulldatamodel.h +++ b/src/scxml/qscxmlnulldatamodel.h @@ -53,7 +53,7 @@ public: explicit QScxmlNullDataModel(QObject *parent = nullptr); ~QScxmlNullDataModel(); - bool setup(const QVariantMap &initialDataValues) Q_DECL_OVERRIDE; + Q_INVOKABLE bool setup(const QVariantMap &initialDataValues) Q_DECL_OVERRIDE; #ifndef Q_QDOC QString evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; diff --git a/src/scxml/qscxmlqstates.cpp b/src/scxml/qscxmlqstates.cpp deleted file mode 100644 index d40ef20..0000000 --- a/src/scxml/qscxmlqstates.cpp +++ /dev/null @@ -1,410 +0,0 @@ -/**************************************************************************** -** -** 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:LGPL$ -** 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 Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** 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-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qscxmlglobals_p.h" -#include "qscxmlqstates_p.h" -#include "qscxmlstatemachine_p.h" - -#undef DUMP_EVENT -#ifdef DUMP_EVENT -#include <QJSEngine> -#include "qscxmlecmascriptdatamodel.h" -#endif - -QT_BEGIN_NAMESPACE - -static QStringList filterEmpty(const QStringList &events) { - QStringList res; - int oldI = 0; - for (int i = 0; i < events.size(); ++i) { - if (events.at(i).isEmpty()) { - res.append(events.mid(oldI, i - oldI)); - oldI = i + 1; - } - } - if (oldI > 0) { - res.append(events.mid(oldI)); - return res; - } - return events; -} - -QScxmlStatePrivate::QScxmlStatePrivate() - : initInstructions(QScxmlExecutableContent::NoInstruction) - , onEntryInstructions(QScxmlExecutableContent::NoInstruction) - , onExitInstructions(QScxmlExecutableContent::NoInstruction) -{} - -QScxmlStatePrivate::~QScxmlStatePrivate() -{ - qDeleteAll(invokableServiceFactories); -} - -QScxmlState::QScxmlState(QState *parent) - : QState(*new QScxmlStatePrivate, parent) -{} - -QScxmlState::QScxmlState(QScxmlStateMachine *parent) - : QState(*new QScxmlStatePrivate, QScxmlStateMachinePrivate::get(parent)->m_qStateMachine) -{} - -QScxmlState::~QScxmlState() -{} - -void QScxmlState::setAsInitialStateFor(QScxmlState *state) -{ - state->setInitialState(this); -} - -void QScxmlState::setAsInitialStateFor(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->setInitialState(this); -} - -QScxmlStateMachine *QScxmlState::stateMachine() const { - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -QString QScxmlState::stateLocation() const -{ - return QStringLiteral("State %1").arg(objectName()); -} - -void QScxmlState::setInitInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlState); - d->initInstructions = instructions; -} - -void QScxmlState::setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlState); - d->onEntryInstructions = instructions; -} - -void QScxmlState::setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlState); - d->onExitInstructions = instructions; -} - -void QScxmlState::setInvokableServiceFactories(const QVector<QScxmlInvokableServiceFactory *> &factories) -{ - Q_D(QScxmlState); - d->invokableServiceFactories = factories; -} - -void QScxmlState::onEntry(QEvent *event) -{ - Q_D(QScxmlState); - - auto sp = QScxmlStateMachinePrivate::get(stateMachine()); - if (d->initInstructions != QScxmlExecutableContent::NoInstruction) { - sp->m_executionEngine->execute(d->initInstructions); - d->initInstructions = QScxmlExecutableContent::NoInstruction; - } - QState::onEntry(event); - auto sm = stateMachine(); - QScxmlStateMachinePrivate::get(sm)->m_executionEngine->execute(d->onEntryInstructions); - foreach (QScxmlInvokableServiceFactory *f, d->invokableServiceFactories) { - if (auto service = f->invoke(stateMachine())) { - d->invokedServices.append(service); - d->servicesWaitingToStart.append(service); - sp->addService(service); - } - } - emit didEnter(); -} - -void QScxmlState::onExit(QEvent *event) -{ - Q_D(QScxmlState); - - emit willExit(); - auto sm = stateMachine(); - QScxmlStateMachinePrivate::get(sm)->m_executionEngine->execute(d->onExitInstructions); - QState::onExit(event); -} - -QScxmlFinalStatePrivate::QScxmlFinalStatePrivate() - : doneData(QScxmlExecutableContent::NoInstruction) - , onEntryInstructions(QScxmlExecutableContent::NoInstruction) - , onExitInstructions(QScxmlExecutableContent::NoInstruction) -{} - -QScxmlFinalStatePrivate::~QScxmlFinalStatePrivate() -{} - -QScxmlFinalState::QScxmlFinalState(QState *parent) - : QFinalState(*new QScxmlFinalStatePrivate, parent) -{} - -QScxmlFinalState::QScxmlFinalState(QScxmlStateMachine *parent) - : QFinalState(*new QScxmlFinalStatePrivate, QScxmlStateMachinePrivate::get(parent)->m_qStateMachine) -{} - -QScxmlFinalState::~QScxmlFinalState() -{} - -void QScxmlFinalState::setAsInitialStateFor(QScxmlState *state) -{ - state->setInitialState(this); -} - -void QScxmlFinalState::setAsInitialStateFor(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->setInitialState(this); -} - -QScxmlStateMachine *QScxmlFinalState::stateMachine() const { - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -QScxmlHistoryState::QScxmlHistoryState(QState *parent) - : QHistoryState(parent) -{ -} - -QScxmlHistoryState::~QScxmlHistoryState() -{ -} - -void QScxmlHistoryState::setAsInitialStateFor(QScxmlState *state) -{ - state->setInitialState(this); -} - -void QScxmlHistoryState::setAsInitialStateFor(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->setInitialState(this); -} - -QScxmlStateMachine *QScxmlHistoryState::stateMachine() const -{ - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -QScxmlExecutableContent::ContainerId QScxmlFinalState::doneData() const -{ - Q_D(const QScxmlFinalState); - return d->doneData; -} - -void QScxmlFinalState::setDoneData(QScxmlExecutableContent::ContainerId doneData) -{ - Q_D(QScxmlFinalState); - d->doneData = doneData; -} - -void QScxmlFinalState::setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlFinalState); - d->onEntryInstructions = instructions; -} - -void QScxmlFinalState::setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlFinalState); - d->onExitInstructions = instructions; -} - -void QScxmlFinalState::onEntry(QEvent *event) -{ - Q_D(QScxmlFinalState); - - QFinalState::onEntry(event); - auto smp = QScxmlStateMachinePrivate::get(stateMachine()); - smp->m_executionEngine->execute(d->onEntryInstructions); -} - -void QScxmlFinalState::onExit(QEvent *event) -{ - Q_D(QScxmlFinalState); - - QFinalState::onExit(event); - QScxmlStateMachinePrivate::get(stateMachine())->m_executionEngine->execute(d->onExitInstructions); -} - -QScxmlBaseTransition::QScxmlBaseTransition(QState *sourceState, const QStringList &eventSelector) - : QAbstractTransition(*new QScxmlBaseTransitionPrivate, sourceState) -{ - Q_D(QScxmlBaseTransition); - d->eventSelector = eventSelector; -} - -QScxmlBaseTransition::QScxmlBaseTransition(QScxmlBaseTransitionPrivate &dd, QState *parent, - const QStringList &eventSelector) - : QAbstractTransition(dd, parent) -{ - Q_D(QScxmlBaseTransition); - d->eventSelector = eventSelector; -} - -QScxmlBaseTransition::~QScxmlBaseTransition() -{} - -QScxmlStateMachine *QScxmlBaseTransition::stateMachine() const { - if (QScxmlInternal::WrappedQStateMachine *t = qobject_cast<QScxmlInternal::WrappedQStateMachine *>(parent())) - return t->stateMachine(); - if (QState *s = sourceState()) - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(s->machine())->stateMachine(); - qCWarning(qscxmlLog) << "could not find Scxml::StateMachine in " << transitionLocation(); - return 0; -} - -QString QScxmlBaseTransition::transitionLocation() const { - if (QState *state = sourceState()) { - QString stateName = state->objectName(); - int transitionIndex = state->transitions().indexOf(const_cast<QScxmlBaseTransition *>(this)); - return QStringLiteral("transition #%1 in state %2").arg(transitionIndex).arg(stateName); - } - return QStringLiteral("unbound transition @%1").arg(reinterpret_cast<quintptr>(this)); -} - -bool QScxmlBaseTransition::eventTest(QEvent *event) -{ - Q_D(QScxmlBaseTransition); - - if (d->eventSelector.isEmpty()) - return true; - if (event->type() == QEvent::None) - return false; - Q_ASSERT(stateMachine()); - QString eventName = QScxmlStateMachinePrivate::get(stateMachine())->m_event.name(); - bool selected = false; - foreach (QString eventStr, d->eventSelector) { - if (eventStr == QStringLiteral("*")) { - selected = true; - break; - } - if (eventStr.endsWith(QStringLiteral(".*"))) - eventStr.chop(2); - if (eventName.startsWith(eventStr)) { - QChar nextC = QLatin1Char('.'); - if (eventName.size() > eventStr.size()) - nextC = eventName.at(eventStr.size()); - if (nextC == QLatin1Char('.') || nextC == QLatin1Char('(')) { - selected = true; - break; - } - } - } - return selected; -} - -void QScxmlBaseTransition::onTransition(QEvent *event) -{ - Q_UNUSED(event); -} - -QScxmlTransitionPrivate::QScxmlTransitionPrivate() - : conditionalExp(QScxmlExecutableContent::NoEvaluator) - , instructionsOnTransition(QScxmlExecutableContent::NoInstruction) -{} - -QScxmlTransitionPrivate::~QScxmlTransitionPrivate() -{} - -QScxmlTransition::QScxmlTransition(QState *sourceState, const QStringList &eventSelector) - : QScxmlBaseTransition(*new QScxmlTransitionPrivate, sourceState, filterEmpty(eventSelector)) -{} - -QScxmlTransition::QScxmlTransition(const QStringList &eventSelector) - : QScxmlBaseTransition(*new QScxmlTransitionPrivate, Q_NULLPTR, filterEmpty(eventSelector)) -{} - -QScxmlTransition::~QScxmlTransition() -{} - -void QScxmlTransition::addTransitionTo(QScxmlState *state) -{ - state->addTransition(this); -} - -void QScxmlTransition::addTransitionTo(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->addTransition(this); -} - -bool QScxmlTransition::eventTest(QEvent *event) -{ - Q_D(QScxmlTransition); - -#ifdef DUMP_EVENT - if (auto edm = dynamic_cast<QScxmlEcmaScriptDataModel *>(stateMachine()->dataModel())) - qCDebug(qscxmlLog) << qPrintable(edm->engine()->evaluate(QLatin1String("JSON.stringify(_event)")).toString()); -#endif - - if (QScxmlBaseTransition::eventTest(event)) { - bool ok = true; - if (d->conditionalExp != QScxmlExecutableContent::NoEvaluator) - return stateMachine()->dataModel()->evaluateToBool(d->conditionalExp, &ok) && ok; - return true; - } - - return false; -} - -void QScxmlTransition::onTransition(QEvent *) -{ - Q_D(QScxmlTransition); - - QScxmlStateMachinePrivate::get(stateMachine())->m_executionEngine->execute(d->instructionsOnTransition); -} - -QScxmlStateMachine *QScxmlTransition::stateMachine() const { - // work around a bug in QStateMachine - if (QScxmlInternal::WrappedQStateMachine *t = qobject_cast<QScxmlInternal::WrappedQStateMachine *>(sourceState())) - return t->stateMachine(); - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -void QScxmlTransition::setInstructionsOnTransition(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlTransition); - d->instructionsOnTransition = instructions; -} - -void QScxmlTransition::setConditionalExpression(QScxmlExecutableContent::EvaluatorId evaluator) -{ - Q_D(QScxmlTransition); - d->conditionalExp = evaluator; -} - -QT_END_NAMESPACE diff --git a/src/scxml/qscxmlqstates.h b/src/scxml/qscxmlqstates.h deleted file mode 100644 index 7280a0f..0000000 --- a/src/scxml/qscxmlqstates.h +++ /dev/null @@ -1,206 +0,0 @@ -/**************************************************************************** -** -** 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:LGPL$ -** 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 Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** 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-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SCXMLQSTATES_H -#define SCXMLQSTATES_H - -#include <QtScxml/qscxmlstatemachine.h> -#include <QtScxml/qscxmlinvokableservice.h> - -#include <QAbstractTransition> -#include <QFinalState> -#include <QHistoryState> -#include <QState> - -QT_BEGIN_NAMESPACE - -template<class T> -class QScxmlInvokeScxmlFactory: public QScxmlInvokableScxmlServiceFactory -{ -public: - QScxmlInvokeScxmlFactory(QScxmlExecutableContent::StringId invokeLocation, - QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, - QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, - bool doAutoforward, - const QVector<Param> ¶ms, - QScxmlExecutableContent::ContainerId finalize) - : QScxmlInvokableScxmlServiceFactory(invokeLocation, id, idPrefix, idlocation, namelist, - doAutoforward, params, finalize) - {} - - QScxmlInvokableService *invoke(QScxmlStateMachine *parent) Q_DECL_OVERRIDE - { - return finishInvoke(new T, parent); - } -}; - -class QScxmlStatePrivate; -class Q_SCXML_EXPORT QScxmlState: public QState -{ - Q_OBJECT - -public: - QScxmlState(QState *parent = Q_NULLPTR); - QScxmlState(QScxmlStateMachine *parent); - ~QScxmlState(); - - void setAsInitialStateFor(QScxmlState *state); - void setAsInitialStateFor(QScxmlStateMachine *stateMachine); - - QScxmlStateMachine *stateMachine() const; - QString stateLocation() const; - - void setInitInstructions(QScxmlExecutableContent::ContainerId instructions); - void setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions); - void setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions); - void setInvokableServiceFactories(const QVector<QScxmlInvokableServiceFactory *>& factories); - -Q_SIGNALS: - void didEnter(); // TODO: REMOVE! - void willExit(); // TODO: REMOVE! - -protected: - void onEntry(QEvent * event) Q_DECL_OVERRIDE; - void onExit(QEvent * event) Q_DECL_OVERRIDE; - -private: - Q_DECLARE_PRIVATE(QScxmlState) -}; - -class QScxmlFinalStatePrivate; -class Q_SCXML_EXPORT QScxmlFinalState: public QFinalState -{ - Q_OBJECT - -public: - QScxmlFinalState(QState *parent = Q_NULLPTR); - QScxmlFinalState(QScxmlStateMachine *parent); - ~QScxmlFinalState(); - - void setAsInitialStateFor(QScxmlState *state); - void setAsInitialStateFor(QScxmlStateMachine *stateMachine); - - QScxmlStateMachine *stateMachine() const; - - QScxmlExecutableContent::ContainerId doneData() const; - void setDoneData(QScxmlExecutableContent::ContainerId doneData); - - void setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions); - void setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions); - -protected: - void onEntry(QEvent * event) Q_DECL_OVERRIDE; - void onExit(QEvent * event) Q_DECL_OVERRIDE; - -private: - Q_DECLARE_PRIVATE(QScxmlFinalState) -}; - -class Q_SCXML_EXPORT QScxmlHistoryState: public QHistoryState -{ - Q_OBJECT - -public: - QScxmlHistoryState(QState *parent = Q_NULLPTR); - ~QScxmlHistoryState(); - - void setAsInitialStateFor(QScxmlState *state); - void setAsInitialStateFor(QScxmlStateMachine *stateMachine); - - QScxmlStateMachine *stateMachine() const; -}; - -class QScxmlBaseTransitionPrivate; -class Q_SCXML_EXPORT QScxmlBaseTransition: public QAbstractTransition -{ - Q_OBJECT - class Data; - -public: - QScxmlBaseTransition(QState * sourceState = Q_NULLPTR, - const QStringList &eventSelector = QStringList()); - ~QScxmlBaseTransition(); - - QScxmlStateMachine *stateMachine() const; - QString transitionLocation() const; - - bool eventTest(QEvent *event) Q_DECL_OVERRIDE; - -protected: - void onTransition(QEvent *event) Q_DECL_OVERRIDE; - - QScxmlBaseTransition(QScxmlBaseTransitionPrivate &dd, QState *parent, - const QStringList &eventSelector = QStringList()); - -private: - Q_DECLARE_PRIVATE(QScxmlBaseTransition) -}; - -class QScxmlTransitionPrivate; -class Q_SCXML_EXPORT QScxmlTransition: public QScxmlBaseTransition -{ - Q_OBJECT - -public: - QScxmlTransition(QState * sourceState = Q_NULLPTR, - const QStringList &eventSelector = QStringList()); - QScxmlTransition(const QStringList &eventSelector); - ~QScxmlTransition(); - - void addTransitionTo(QScxmlState *state); - void addTransitionTo(QScxmlStateMachine *stateMachine); - - bool eventTest(QEvent *event) Q_DECL_OVERRIDE; - QScxmlStateMachine *stateMachine() const; - - void setInstructionsOnTransition(QScxmlExecutableContent::ContainerId instructions); - void setConditionalExpression(QScxmlExecutableContent::EvaluatorId evaluator); - -protected: - void onTransition(QEvent *event) Q_DECL_OVERRIDE; - -private: - Q_DECLARE_PRIVATE(QScxmlTransition) -}; - -QT_END_NAMESPACE - -#endif // SCXMLQSTATES_H diff --git a/src/scxml/qscxmlstatemachine.cpp b/src/scxml/qscxmlstatemachine.cpp index 6603703..ef5a312 100644 --- a/src/scxml/qscxmlstatemachine.cpp +++ b/src/scxml/qscxmlstatemachine.cpp @@ -41,7 +41,6 @@ #include "qscxmlexecutablecontent_p.h" #include "qscxmlevent_p.h" #include "qscxmlinvokableservice.h" -#include "qscxmlqstates_p.h" #include "qscxmldatamodel_p.h" #include <QAbstractState> @@ -50,11 +49,9 @@ #include <QHash> #include <QJSEngine> #include <QLoggingCategory> -#include <QState> #include <QString> #include <QTimer> - -#include <QtCore/private/qstatemachine_p.h> +#include <QThread> #include <functional> @@ -63,232 +60,370 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qscxmlLog, "qt.scxml.statemachine") Q_LOGGING_CATEGORY(scxmlLog, "scxml.statemachine") -namespace QScxmlInternal { -class WrappedQStateMachinePrivate: public QStateMachinePrivate -{ - Q_DECLARE_PUBLIC(WrappedQStateMachine) - -public: - WrappedQStateMachinePrivate(QScxmlStateMachine *stateMachine) - : m_stateMachine(stateMachine) - , m_queuedEvents(Q_NULLPTR) - {} - ~WrappedQStateMachinePrivate() - { - if (m_queuedEvents) { - foreach (const QueuedEvent &qt, *m_queuedEvents) { - delete qt.event; - } +/*! + * \class QScxmlStateMachine + * \brief The QScxmlStateMachine class provides an interface to the state machines + * created from SCXML files. + * \since 5.7 + * \inmodule QtScxml + * + * QScxmlStateMachine is an implementation of the + * \l{SCXML Specification}{State Chart XML (SCXML)}. + * + * All states that are defined in the SCXML file + * are accessible as properties of QScxmlStateMachine. + * These properties are boolean values and indicate + * whether the state is active or inactive. + * + * \note The QScxmlStateMachine needs a QEventLoop to work correctly. The event loop is used to + * implement the \c delay attribute for events and to schedule the processing of a state + * machine when events are received from nested (or parent) state machines. + */ - delete m_queuedEvents; - } - } +/*! + \fn QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec, + const QObject *receiver, + PointerToMemberFunction method, + Qt::ConnectionType type) - int eventIdForDelayedEvent(const QString &sendId); - - QScxmlStateMachine *stateMachine() const - { return m_stateMachine; } - - QScxmlStateMachinePrivate *stateMachinePrivate() const - { return QScxmlStateMachinePrivate::get(stateMachine()); } - -protected: // overrides for QStateMachinePrivate: - void noMicrostep() Q_DECL_OVERRIDE; - void processedPendingEvents(bool didChange) Q_DECL_OVERRIDE; - void beginMacrostep() Q_DECL_OVERRIDE; - void endMacrostep(bool didChange) Q_DECL_OVERRIDE; - - void enterStates(QEvent *event, const QList<QAbstractState*> &exitedStates_sorted, - const QList<QAbstractState*> &statesToEnter_sorted, - const QSet<QAbstractState*> &statesForDefaultEntry, - QHash<QAbstractState *, QVector<QPropertyAssignment> > &propertyAssignmentsForState -# ifndef QT_NO_ANIMATION - , const QList<QAbstractAnimation*> &selectedAnimations -# endif - ) Q_DECL_OVERRIDE; - void exitStates(QEvent *event, const QList<QAbstractState *> &statesToExit_sorted, - const QHash<QAbstractState*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) Q_DECL_OVERRIDE; - - void exitInterpreter() Q_DECL_OVERRIDE; - - void emitStateFinished(QState *forState, QFinalState *guiltyState) Q_DECL_OVERRIDE; - void startupHook() Q_DECL_OVERRIDE; - -public: // fields - QScxmlStateMachine *m_stateMachine; - - struct QueuedEvent - { - QueuedEvent(QEvent *event = Q_NULLPTR, QStateMachine::EventPriority priority = QStateMachine::NormalPriority) - : event(event) - , priority(priority) - {} - - QEvent *event; - QStateMachine::EventPriority priority; - }; - QVector<QueuedEvent> *m_queuedEvents; -}; - -WrappedQStateMachine::WrappedQStateMachine(QScxmlStateMachine *parent) - : QStateMachine(*new WrappedQStateMachinePrivate(parent), parent) -{} + Creates a connection of the given \a type from the event specified by + \a scxmlEventSpec to \a method in the \a receiver object. -WrappedQStateMachine::WrappedQStateMachine(WrappedQStateMachinePrivate &dd, QScxmlStateMachine *parent) - : QStateMachine(dd, parent) -{} + The receiver's \a method must take a QScxmlEvent as a parameter. -QScxmlStateMachine *WrappedQStateMachine::stateMachine() const -{ - Q_D(const WrappedQStateMachine); + In contrast to event specifications in SCXML documents, spaces are not + allowed in the \a scxmlEventSpec here. In order to connect to multiple + events with different prefixes, connectToEvent() has to be called multiple + times. - return d->stateMachine(); -} + Returns a handle to the connection, which can be used later to disconnect. +*/ -QScxmlStateMachinePrivate *WrappedQStateMachine::stateMachinePrivate() -{ - Q_D(const WrappedQStateMachine); +/*! + \fn QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec, + Functor functor, + Qt::ConnectionType type) - return d->stateMachinePrivate(); -} -} // Internal namespace + Creates a connection of the given \a type from the event specified by + \a scxmlEventSpec to \a functor. + + The \a functor must take a QScxmlEvent as a parameter. + + In contrast to event specifications in SCXML documents, spaces are not + allowed in the \a scxmlEventSpec here. In order to connect to multiple + events with different prefixes, connectToEvent() has to be called multiple + times. + + Returns a handle to the connection, which can be used later to disconnect. +*/ /*! - * \class QScxmlEventFilter - * \brief The QScxmlEventFilter class is an event filter for an SCXML state machine. - * \since 5.7 - * \inmodule QtScxml - * - * An event filter can be used to intercept events generated by an SCXML state machine. By default, - * the QScxmlStateMachine will have an event filter that will intercept events that are marked as - * external and that have the type \c qt:signal to emit signals. - * - * \sa QScxmlStateMachine + \fn QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec, + const QObject *context, + Functor functor, + Qt::ConnectionType type) + + Creates a connection of the given \a type from the event specified by + \a scxmlEventSpec to \a functor using \a context as context. + + The \a functor must take a QScxmlEvent as a parameter. + + In contrast to event specifications in SCXML documents, spaces are not + allowed in the \a scxmlEventSpec here. In order to connect to multiple + events with different prefixes, connectToEvent() has to be called multiple + times. + + Returns a handle to the connection, which can be used later to disconnect. +*/ + +/*! + \fn QScxmlStateMachine::connectToState(const QString &scxmlStateName, + const QObject *receiver, + PointerToMemberFunction method, + Qt::ConnectionType type) + + Creates a connection of the given \a type from the state specified by + \a scxmlStateName to \a method in the \a receiver object. + + The receiver's \a method must take a boolean argument that indicates + whether the state connected became active or inactive. + + Returns a handle to the connection, which can be used later to disconnect. +*/ + + +/*! + \fn QScxmlStateMachine::connectToState(const QString &scxmlStateName, + Functor functor, + Qt::ConnectionType type) + + Creates a connection of the given \a type from the state specified by + \a scxmlStateName to \a functor. + + The \a functor must take a boolean argument that indicates whether the + state connected became active or inactive. + + Returns a handle to the connection, which can be used later to disconnect. +*/ + +/*! + \fn QScxmlStateMachine::connectToState(const QString &scxmlStateName, + const QObject *context, + Functor functor, + Qt::ConnectionType type) + + Creates a connection of the given \a type from the state specified by + \a scxmlStateName to \a functor using \a context as context. + + The \a functor must take a boolean argument that indicates whether the + state connected became active or inactive. + + Returns a handle to the connection, which can be used later to disconnect. +*/ + +/*! + \fn QScxmlStateMachine::onEntry(const QObject *receiver, + const char *method) + + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver using QMetaObject::invokeMethod() if that argument + is \c true and \a receiver has not been deleted, yet. + + The given \a method must not accept any arguments. \a method is the plain + method name, not enclosed in \c SIGNAL() or \c SLOT(). + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is entered. */ /*! - * Destroys the SCXML event filter. + \fn QScxmlStateMachine::onExit(const QObject *receiver, const char *method) + + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver using QMetaObject::invokeMethod() if that argument + is \c false and \a receiver has not been deleted, yet. + + The given \a method must not accept any arguments. \a method is the plain + method name, not enclosed in SIGNAL(...) or SLOT(...). + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is left. */ -QScxmlEventFilter::~QScxmlEventFilter() -{} /*! - * \fn QScxmlEventFilter::handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) - * - * Returns \c true if the \a event should be submitted to the \a stateMachine - * and it was not intercepted inside the event filter, \c false otherwise. + \fn QScxmlStateMachine::onEntry(Functor functor) + + Returns a functor that accepts a boolean argument and calls the given + \a functor if that argument is \c true. The given \a functor must not + accept any arguments. + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is entered. */ /*! - * \class QScxmlStateMachine - * \brief The QScxmlStateMachine class provides an interface to the state machines - * created from SCXML files. - * \since 5.7 - * \inmodule QtScxml - * - * QScxmlStateMachine is an implementation of the - * \l{SCXML Specification}{State Chart XML (SCXML)}. - * - * All states that are defined in the SCXML file - * are accessible as properties of QScxmlStateMachine. - * These properties are boolean values and indicate - * whether the state is active or inactive. - * - * All external signals defined inside the SCXML file - * that are of the \c qt:signal type, are accessible - * as signals of QScxmlStateMachine in the \e {Qt mode}. - * The only argument of these signals - * is always QVariant, which is of QMap<QString, QVariant> - * type containing the content of all the \c <param> - * elements specified as children of a \c <send> element. - * The name of each QScxmlStateMachine signal - * corresponds to the value defined in the - * \e event attribute of one \c <send> tag in the SCXML file. + \fn QScxmlStateMachine::onExit(Functor functor) + + Returns a functor that accepts a boolean argument and calls the given + \a functor if that argument is \c false. The given \a functor must not + accept any arguments. + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is left. */ /*! - \fn QScxmlStateMachine::eventOccurred(const QScxmlEvent &event) + \fn QScxmlStateMachine::onEntry(const QObject *receiver, + PointerToMemberFunction method) - This signal is emitted when the SCXML event \a event occurs. This signal is - emitted for all events. + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver if that argument is \c true and the \a receiver + has not been deleted, yet. The given \a method must not accept any + arguments. - \sa externalEventOccurred() -*/ + This is useful to wrap handlers for connectToState() that should only + be executed when the state is entered. + */ /*! - \fn QScxmlStateMachine::externalEventOccurred(const QScxmlEvent &event) + \fn QScxmlStateMachine::onExit(const QObject *receiver, + PointerToMemberFunction method) - This signal is emitted for each \c <send> element in the SCXML file that - contains the attribute \c {type="qt:signal"}. The event that occurred is - specified by \a event. + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver if that argument is \c false and the \a receiver + has not been deleted, yet. The given \a method must not accept any + arguments. - \sa eventOccurred() -*/ + This is useful to wrap handlers for connectToState() that should only + be executed when the state is left. + */ -QAtomicInt QScxmlStateMachinePrivate::m_sessionIdCounter = QAtomicInt(0); +namespace QScxmlInternal { +static int signalIndex(const QMetaObject *meta, const QByteArray &signalName) +{ + Q_ASSERT(meta); -QScxmlStateMachinePrivate::QScxmlStateMachinePrivate() - : QObjectPrivate() - , m_sessionId(QScxmlStateMachine::generateSessionId(QStringLiteral("session-"))) - , m_isInvoked(false) - , m_isInitialized(false) - , m_dataModel(Q_NULLPTR) - , m_dataBinding(QScxmlStateMachine::EarlyBinding) - , m_executionEngine(Q_NULLPTR) - , m_tableData(Q_NULLPTR) - , m_qStateMachine(Q_NULLPTR) - , m_eventFilter(Q_NULLPTR) - , m_parentStateMachine(Q_NULLPTR) -{} + int signalIndex = meta->indexOfSignal(signalName.constData()); -QScxmlStateMachinePrivate::~QScxmlStateMachinePrivate() + // If signal doesn't exist, return negative value + if (signalIndex < 0) + return signalIndex; + + // signal belongs to class whose meta object was passed, not some derived class. + Q_ASSERT(meta->methodOffset() <= signalIndex); + + // Duplicate of computeOffsets in qobject.cpp + const QMetaObject *m = meta->d.superdata; + while (m) { + const QMetaObjectPrivate *d = QMetaObjectPrivate::get(m); + signalIndex = signalIndex - d->methodCount + d->signalCount; + m = m->d.superdata; + } + + // Asserting about the signal not being cloned would be nice, too, but not practical. + + return signalIndex; +} + +void EventLoopHook::queueProcessEvents() { - qDeleteAll(m_invokedServices); - delete m_executionEngine; + if (smp->m_isProcessingEvents) + return; + + QMetaObject::invokeMethod(this, "doProcessEvents", Qt::QueuedConnection); } -void QScxmlStateMachinePrivate::init() +void EventLoopHook::doProcessEvents() { - Q_Q(QScxmlStateMachine); - m_executionEngine = new QScxmlExecutableContent::QScxmlExecutionEngine(q); - setQStateMachine(new QScxmlInternal::WrappedQStateMachine(q)); - QObject::connect(m_qStateMachine, &QStateMachine::runningChanged, - q, &QScxmlStateMachine::runningChanged); - QObject::connect(m_qStateMachine, &QStateMachine::finished, - q, &QScxmlStateMachine::finished); + smp->processEvents(); +} - // The final state is also a stable state. - QObject::connect(m_qStateMachine, &QStateMachine::finished, - q, &QScxmlStateMachine::reachedStableState); +void EventLoopHook::timerEvent(QTimerEvent *timerEvent) +{ + const int timerId = timerEvent->timerId(); + for (auto it = smp->m_delayedEvents.begin(), eit = smp->m_delayedEvents.end(); it != eit; ++it) { + if (it->first == timerId) { + QScxmlEvent *scxmlEvent = it->second; + smp->m_delayedEvents.erase(it); + smp->routeEvent(scxmlEvent); + return; + } + } +} + +void ScxmlEventRouter::route(const QStringList &segments, QScxmlEvent *event) +{ + emit eventOccurred(*event); + if (!segments.isEmpty()) { + auto it = children.find(segments.first()); + if (it != children.end()) + it.value()->route(segments.mid(1), event); + } +} + +static QString nextSegment(const QStringList &segments) +{ + if (segments.isEmpty()) + return QString(); + + const QString &segment = segments.first(); + return segment == QLatin1String("*") ? QString() : segment; } -void QScxmlStateMachinePrivate::setQStateMachine(QScxmlInternal::WrappedQStateMachine *stateMachine) +ScxmlEventRouter *ScxmlEventRouter::child(const QString &segment) { - m_qStateMachine = stateMachine; + ScxmlEventRouter *&child = children[segment]; + if (child == nullptr) + child = new ScxmlEventRouter(this); + return child; } -static QAbstractState *findState(const QString &scxmlName, QStateMachine *parent) +void ScxmlEventRouter::disconnectNotify(const QMetaMethod &signal) { - QList<QObject *> worklist; - worklist.reserve(parent->children().size() + parent->configuration().size()); - worklist.append(parent); + Q_UNUSED(signal); + + // Defer the actual work, as this may be called from a destructor, or the signal may not + // actually be disconnected, yet. + QTimer::singleShot(0, this, [this] { + if (!children.isEmpty() || receivers(SIGNAL(eventOccurred(QScxmlEvent))) > 0) + return; + + ScxmlEventRouter *parentRouter = qobject_cast<ScxmlEventRouter *>(parent()); + if (!parentRouter) // root node + return; - while (!worklist.isEmpty()) { - QObject *obj = worklist.takeLast(); - if (QAbstractState *state = qobject_cast<QAbstractState *>(obj)) { - if (state->objectName() == scxmlName) - return state; + QHash<QString, ScxmlEventRouter *>::Iterator it = parentRouter->children.begin(), + end = parentRouter->children.end(); + for (; it != end; ++it) { + if (it.value() == this) { + parentRouter->children.erase(it); + parentRouter->disconnectNotify(QMetaMethod()); + break; + } } - worklist.append(obj->children()); - } - return Q_NULLPTR; + deleteLater(); // The parent might delete itself, triggering QObject delete cascades. + }); } -QAbstractState *QScxmlStateMachinePrivate::stateByScxmlName(const QString &scxmlName) +QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments, + const QObject *receiver, + const char *method, + Qt::ConnectionType type) { - return findState(scxmlName, m_qStateMachine); + QString segment = nextSegment(segments); + return segment.isEmpty() ? + connect(this, SIGNAL(eventOccurred(QScxmlEvent)), receiver, method, type) : + child(segment)->connectToEvent(segments.mid(1), receiver, method, type); +} + +QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *method, + Qt::ConnectionType type) +{ + QString segment = nextSegment(segments); + if (segment.isEmpty()) { + const int *types = Q_NULLPTR; + if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) + types = QtPrivate::ConnectionTypes<QtPrivate::List<QScxmlEvent> >::types(); + + const QMetaObject *meta = metaObject(); + static const int eventOccurredIndex = signalIndex(meta, "eventOccurred(QScxmlEvent)"); + return QObjectPrivate::connectImpl(this, eventOccurredIndex, receiver, slot, method, type, + types, meta); + } else { + return child(segment)->connectToEvent(segments.mid(1), receiver, slot, method, type); + } +} + +} // namespace QScxmlInternal + +QAtomicInt QScxmlStateMachinePrivate::m_sessionIdCounter = QAtomicInt(0); + +QScxmlStateMachinePrivate::QScxmlStateMachinePrivate(const QMetaObject *metaObject) + : QObjectPrivate() + , m_sessionId(QScxmlStateMachinePrivate::generateSessionId(QStringLiteral("session-"))) + , m_isInvoked(false) + , m_isInitialized(false) + , m_isProcessingEvents(false) + , m_dataModel(Q_NULLPTR) + , m_loader(&m_defaultLoader) + , m_executionEngine(Q_NULLPTR) + , m_tableData(Q_NULLPTR) + , m_parentStateMachine(Q_NULLPTR) + , m_eventLoopHook(this) + , m_metaObject(metaObject) + , m_infoSignalProxy(nullptr) +{} + +QScxmlStateMachinePrivate::~QScxmlStateMachinePrivate() +{ + for (const InvokedService &invokedService : m_invokedServices) + delete invokedService.service; + qDeleteAll(m_cachedFactories); + delete m_executionEngine; } QScxmlStateMachinePrivate::ParserData *QScxmlStateMachinePrivate::parserData() @@ -298,23 +433,51 @@ QScxmlStateMachinePrivate::ParserData *QScxmlStateMachinePrivate::parserData() return m_parserData.data(); } -void QScxmlStateMachinePrivate::addService(QScxmlInvokableService *service) +void QScxmlStateMachinePrivate::addService(int invokingState) { Q_Q(QScxmlStateMachine); - Q_ASSERT(!m_invokedServices.contains(service)); - m_invokedServices.append(service); - q->setService(service->name(), service); + + const int arrayId = m_stateTable->state(invokingState).serviceFactoryIds; + if (arrayId == StateTable::InvalidIndex) + return; + + const auto &ids = m_stateTable->array(arrayId); + for (int id : ids) { + auto factory = serviceFactory(id); + auto service = factory->invoke(q); + if (service == nullptr) + continue; // service failed to start + const QString serviceName = service->name(); + m_invokedServices[size_t(id)] = { invokingState, service, serviceName }; + service->start(factory->invokeInfo(), factory->parameters(), factory->names()); + } + emitInvokedServicesChanged(); } -bool QScxmlStateMachinePrivate::removeService(QScxmlInvokableService *service) +void QScxmlStateMachinePrivate::removeService(int invokingState) { - Q_Q(QScxmlStateMachine); - Q_ASSERT(m_invokedServices.contains(service)); - if (m_invokedServices.removeOne(service)) { - q->setService(service->name(), Q_NULLPTR); - return true; + const int arrayId = m_stateTable->state(invokingState).serviceFactoryIds; + if (arrayId == StateTable::InvalidIndex) + return; + + for (size_t i = 0, ei = m_invokedServices.size(); i != ei; ++i) { + auto &it = m_invokedServices[i]; + QScxmlInvokableService *service = it.service; + if (it.invokingState == invokingState && service != nullptr) { + it.service = nullptr; + delete service; + } } - return false; + emitInvokedServicesChanged(); +} + +QScxmlInvokableServiceFactory *QScxmlStateMachinePrivate::serviceFactory(int id) +{ + Q_ASSERT(id <= m_stateTable->maxServiceId && id >= 0); + QScxmlInvokableServiceFactory *& factory = m_cachedFactories[size_t(id)]; + if (factory == nullptr) + factory = m_tableData->serviceFactory(id); + return factory; } bool QScxmlStateMachinePrivate::executeInitialSetup() @@ -341,11 +504,14 @@ void QScxmlStateMachinePrivate::routeEvent(QScxmlEvent *event) } else if (origin.startsWith(QStringLiteral("#_")) && origin != QStringLiteral("#_internal")) { // route to children auto originId = origin.midRef(2); - foreach (QScxmlInvokableService *service, m_invokedServices) { + for (const auto &invokedService : m_invokedServices) { + auto service = invokedService.service; + if (service == nullptr) + continue; if (service->id() == originId) { qCDebug(qscxmlLog) << q << "routing event" << event->name() - << "from" << q->name() - << "to parent" << service->id(); + << "from" << q->name() + << "to child" << service->id(); service->postEvent(new QScxmlEvent(*event)); } } @@ -359,17 +525,58 @@ void QScxmlStateMachinePrivate::postEvent(QScxmlEvent *event) { Q_Q(QScxmlStateMachine); - QStateMachine::EventPriority priority = - event->eventType() == QScxmlEvent::ExternalEvent ? QStateMachine::NormalPriority - : QStateMachine::HighPriority; + if (!event->name().startsWith(QStringLiteral("done.invoke."))) { + for (int id = 0, end = static_cast<int>(m_invokedServices.size()); id != end; ++id) { + auto service = m_invokedServices[id].service; + if (service == nullptr) + continue; + auto factory = serviceFactory(id); + if (event->invokeId() == service->id()) { + setEvent(event); + service->finalize(factory->invokeInfo().finalize); + resetEvent(); + } + if (factory->invokeInfo().autoforward) { + qCDebug(qscxmlLog) << q << "auto-forwarding event" << event->name() + << "from" << q->name() + << "to child" << service->id(); + service->postEvent(new QScxmlEvent(*event)); + } + } + } + + if (event->eventType() == QScxmlEvent::ExternalEvent) + m_router.route(event->name().split(QLatin1Char('.')), event); - if (m_qStateMachine->isRunning()) { - qCDebug(qscxmlLog) << q << "posting event" << event->name(); - m_qStateMachine->postEvent(event, priority); + if (event->eventType() == QScxmlEvent::ExternalEvent) { + qCDebug(qscxmlLog) << q << "posting external event" << event->name(); + m_externalQueue.enqueue(event); } else { - qCDebug(qscxmlLog) << q << "queueing event" << event->name(); - m_qStateMachine->queueEvent(event, priority); + qCDebug(qscxmlLog) << q << "posting internal event" << event->name(); + m_internalQueue.enqueue(event); } + + m_eventLoopHook.queueProcessEvents(); +} + +void QScxmlStateMachinePrivate::submitDelayedEvent(QScxmlEvent *event) +{ + Q_ASSERT(event); + Q_ASSERT(event->delay() > 0); + + const int timerId = m_eventLoopHook.startTimer(event->delay()); + if (timerId == 0) { + qWarning("QScxmlStateMachinePrivate::submitDelayedEvent: " + "failed to start timer for event '%s' (%p)", + qPrintable(event->name()), event); + delete event; + return; + } + m_delayedEvents.push_back(std::make_pair(timerId, event)); + + qCDebug(qscxmlLog) << q_func() + << ": delayed event" << event->name() + << "(" << event << ") got id:" << timerId; } /*! @@ -390,6 +597,797 @@ void QScxmlStateMachinePrivate::submitError(const QString &type, const QString & q->submitEvent(QScxmlEventBuilder::errorEvent(q, type, msg, sendid)); } +void QScxmlStateMachinePrivate::start() +{ + if (m_stateTable->binding == StateTable::LateBinding) + m_isFirstStateEntry.resize(m_stateTable->stateCount, true); + + m_runningState = Starting; + Q_ASSERT(m_stateTable->initialTransition != StateTable::InvalidIndex); +} + +void QScxmlStateMachinePrivate::pause() +{ + if (isRunnable() && !isPaused()) + m_runningState = Paused; +} + +void QScxmlStateMachinePrivate::processEvents() +{ + if (m_isProcessingEvents || (!isRunnable() && !isPaused())) + return; + + m_isProcessingEvents = true; + + Q_Q(QScxmlStateMachine); + qCDebug(qscxmlLog) << q_func() << "starting macrostep"; + + while (isRunnable() && !isPaused()) { + if (m_runningState == Starting) { + enterStates({m_stateTable->initialTransition}); + if (m_runningState == Starting) + m_runningState = Running; + continue; + } + + OrderedSet enabledTransitions; + std::vector<int> configurationInDocumentOrder = m_configuration.list(); + std::sort(configurationInDocumentOrder.begin(), configurationInDocumentOrder.end()); + selectTransitions(enabledTransitions, configurationInDocumentOrder, nullptr); + if (!enabledTransitions.isEmpty()) { + microstep(enabledTransitions); + } else if (!m_internalQueue.isEmpty()) { + auto event = m_internalQueue.dequeue(); + setEvent(event); + selectTransitions(enabledTransitions, configurationInDocumentOrder, event); + if (!enabledTransitions.isEmpty()) { + microstep(enabledTransitions); + } + resetEvent(); + delete event; + } else if (!m_externalQueue.isEmpty()) { + auto event = m_externalQueue.dequeue(); + setEvent(event); + selectTransitions(enabledTransitions, configurationInDocumentOrder, event); + if (!enabledTransitions.isEmpty()) { + microstep(enabledTransitions); + } + resetEvent(); + delete event; + } else { + // nothing to do, so: + break; + } + } + + if (!m_statesToInvoke.empty()) { + for (int stateId : m_statesToInvoke) + addService(stateId); + m_statesToInvoke.clear(); + } + + qCDebug(qscxmlLog) << q_func() + << "finished macrostep, runnable:" << isRunnable() + << "paused:" << isPaused(); + emit q->reachedStableState(); + if (!isRunnable() && !isPaused()) { + exitInterpreter(); + emit q->finished(); + } + + m_isProcessingEvents = false; +} + +void QScxmlStateMachinePrivate::setEvent(QScxmlEvent *event) +{ + Q_ASSERT(event); + m_dataModel->setScxmlEvent(*event); +} + +void QScxmlStateMachinePrivate::resetEvent() +{ + m_dataModel->setScxmlEvent(QScxmlEvent()); +} + +void QScxmlStateMachinePrivate::emitStateActive(int stateIndex, bool active) +{ + Q_Q(QScxmlStateMachine); + void *args[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&active)) }; + QMetaObject::activate(q, m_metaObject, stateIndex, args); +} + +void QScxmlStateMachinePrivate::emitInvokedServicesChanged() +{ + Q_Q(QScxmlStateMachine); + emit q->invokedServicesChanged(q->invokedServices()); +} + +void QScxmlStateMachinePrivate::attach(QScxmlStateMachineInfo *info) +{ + Q_Q(QScxmlStateMachine); + + if (!m_infoSignalProxy) + m_infoSignalProxy = new QScxmlInternal::StateMachineInfoProxy(q); + + QObject::connect(m_infoSignalProxy, &QScxmlInternal::StateMachineInfoProxy::statesEntered, + info, &QScxmlStateMachineInfo::statesEntered); + QObject::connect(m_infoSignalProxy, &QScxmlInternal::StateMachineInfoProxy::statesExited, + info, &QScxmlStateMachineInfo::statesExited); + QObject::connect(m_infoSignalProxy,&QScxmlInternal::StateMachineInfoProxy::transitionsTriggered, + info, &QScxmlStateMachineInfo::transitionsTriggered); +} + +QStringList QScxmlStateMachinePrivate::stateNames(const std::vector<int> &stateIndexes) const +{ + QStringList names; + for (int idx : stateIndexes) + names.append(m_tableData->string(m_stateTable->state(idx).name)); + return names; +} + +std::vector<int> QScxmlStateMachinePrivate::historyStates(int stateIdx) const { + const StateTable::Array kids = m_stateTable->array(m_stateTable->state(stateIdx).childStates); + std::vector<int> res; + if (!kids.isValid()) return res; + for (int k : kids) { + if (m_stateTable->state(k).isHistoryState()) + res.push_back(k); + } + return res; +} + +void QScxmlStateMachinePrivate::exitInterpreter() +{ + qCDebug(qscxmlLog) << q_func() << "exiting SCXML processing"; + + for (auto it : m_delayedEvents) { + m_eventLoopHook.killTimer(it.first); + delete it.second; + } + m_delayedEvents.clear(); + + auto statesToExitSorted = m_configuration.list(); + std::sort(statesToExitSorted.begin(), statesToExitSorted.end(), std::greater<int>()); + for (int stateIndex : statesToExitSorted) { + const auto &state = m_stateTable->state(stateIndex); + if (state.exitInstructions != StateTable::InvalidIndex) { + m_executionEngine->execute(state.exitInstructions); + } + removeService(stateIndex); + if (state.type == StateTable::State::Final && state.parentIsScxmlElement()) { + returnDoneEvent(state.doneData); + } + } +} + +void QScxmlStateMachinePrivate::returnDoneEvent(QScxmlExecutableContent::ContainerId doneData) +{ + Q_Q(QScxmlStateMachine); + + m_executionEngine->execute(doneData, QVariant()); + if (m_isInvoked) { + auto e = new QScxmlEvent; + e->setName(QStringLiteral("done.invoke.") + q->sessionId()); + e->setInvokeId(q->sessionId()); + QScxmlStateMachinePrivate::get(m_parentStateMachine)->postEvent(e); + } +} + +bool QScxmlStateMachinePrivate::nameMatch(const StateTable::Array &patterns, + QScxmlEvent *event) const +{ + const QString eventName = event->name(); + bool selected = false; + for (int eventSelectorIter = 0; eventSelectorIter < patterns.size(); ++eventSelectorIter) { + QString eventStr = m_tableData->string(patterns[eventSelectorIter]); + if (eventStr == QStringLiteral("*")) { + selected = true; + break; + } + if (eventStr.endsWith(QStringLiteral(".*"))) + eventStr.chop(2); + if (eventName.startsWith(eventStr)) { + QChar nextC = QLatin1Char('.'); + if (eventName.size() > eventStr.size()) + nextC = eventName.at(eventStr.size()); + if (nextC == QLatin1Char('.') || nextC == QLatin1Char('(')) { + selected = true; + break; + } + } + } + return selected; +} + +void QScxmlStateMachinePrivate::selectTransitions(OrderedSet &enabledTransitions, + const std::vector<int> &configInDocumentOrder, + QScxmlEvent *event) const +{ + if (event == nullptr) { + qCDebug(qscxmlLog) << q_func() << "selectEventlessTransitions"; + } else { + qCDebug(qscxmlLog) << q_func() << "selectTransitions with event" + << QScxmlEventPrivate::debugString(event).constData(); + } + + std::vector<int> states; + states.reserve(16); + for (int configStateIdx : configInDocumentOrder) { + if (m_stateTable->state(configStateIdx).isAtomic()) { + states.clear(); + states.push_back(configStateIdx); + getProperAncestors(&states, configStateIdx, -1); + for (int stateIdx : states) { + bool finishedWithThisConfigState = false; + + if (stateIdx == -1) { + // the state machine has no transitions (other than the initial one, which has + // already been taken at this point) + continue; + } + const auto &state = m_stateTable->state(stateIdx); + const StateTable::Array transitions = m_stateTable->array(state.transitions); + if (!transitions.isValid()) + continue; + std::vector<int> sortedTransitions(transitions.size(), -1); + std::copy(transitions.begin(), transitions.end(), sortedTransitions.begin()); + for (int transitionIndex : sortedTransitions) { + const StateTable::Transition &t = m_stateTable->transition(transitionIndex); + bool enabled = false; + if (event == nullptr) { + if (t.events == -1) { + if (t.condition == -1) { + enabled = true; + } else { + bool ok = false; + enabled = m_dataModel->evaluateToBool(t.condition, &ok) && ok; + } + } + } else { + if (t.events != -1 && nameMatch(m_stateTable->array(t.events), event)) { + if (t.condition == -1) { + enabled = true; + } else { + bool ok = false; + enabled = m_dataModel->evaluateToBool(t.condition, &ok) && ok; + } + } + } + if (enabled) { + enabledTransitions.add(transitionIndex); + finishedWithThisConfigState = true; + break; // stop iterating over transitions + } + } + + if (finishedWithThisConfigState) + break; // stop iterating over ancestors + } + } + } + if (!enabledTransitions.isEmpty()) + removeConflictingTransitions(&enabledTransitions); +} + +void QScxmlStateMachinePrivate::removeConflictingTransitions(OrderedSet *enabledTransitions) const +{ + Q_ASSERT(enabledTransitions); + + auto sortedTransitions = enabledTransitions->takeList(); + std::sort(sortedTransitions.begin(), sortedTransitions.end(), [this](int t1, int t2) -> bool { + auto descendantDepth = [this](int state, int ancestor)->int { + int depth = 0; + for (int it = state; it != -1; it = m_stateTable->state(it).parent) { + if (it == ancestor) + break; + ++depth; + } + return depth; + }; + + const auto &s1 = m_stateTable->transition(t1).source; + const auto &s2 = m_stateTable->transition(t2).source; + if (s1 == s2) { + return t1 < t2; + } else if (isDescendant(s1, s2)) { + return true; + } else if (isDescendant(s2, s1)) { + return false; + } else { + const int lcca = findLCCA({ s1, s2 }); + const int s1Depth = descendantDepth(s1, lcca); + const int s2Depth = descendantDepth(s2, lcca); + if (s1Depth == s2Depth) + return s1 < s2; + else + return s1Depth > s2Depth; + } + }); + + OrderedSet filteredTransitions; + for (int t1 : sortedTransitions) { + OrderedSet transitionsToRemove; + bool t1Preempted = false; + OrderedSet exitSetT1; + computeExitSet({t1}, exitSetT1); + const int source1 = m_stateTable->transition(t1).source; + for (int t2 : filteredTransitions) { + OrderedSet exitSetT2; + computeExitSet({t2}, exitSetT2); + if (exitSetT1.intersectsWith(exitSetT2)) { + const int source2 = m_stateTable->transition(t2).source; + if (isDescendant(source1, source2)) { + transitionsToRemove.add(t2); + } else { + t1Preempted = true; + break; + } + } + } + if (!t1Preempted) { + for (int t3 : transitionsToRemove) { + filteredTransitions.remove(t3); + } + filteredTransitions.add(t1); + } + } + *enabledTransitions = filteredTransitions; +} + +void QScxmlStateMachinePrivate::getProperAncestors(std::vector<int> *ancestors, int state1, + int state2) const +{ + Q_ASSERT(ancestors); + + if (state1 == -1) { + return; + } + + int parent = state1; + do { + parent = m_stateTable->state(parent).parent; + if (parent == state2) { + break; + } + ancestors->push_back(parent); + } while (parent != -1); +} + +void QScxmlStateMachinePrivate::microstep(const OrderedSet &enabledTransitions) +{ + if (qscxmlLog().isDebugEnabled()) { + qCDebug(qscxmlLog) << q_func() + << "starting microstep, configuration:" + << stateNames(m_configuration.list()); + qCDebug(qscxmlLog) << q_func() << "enabled transitions:"; + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + QString from = QStringLiteral("(none)"); + if (transition.source != StateTable::InvalidIndex) + from = m_tableData->string(m_stateTable->state(transition.source).name); + QStringList to; + if (transition.targets == StateTable::InvalidIndex) { + to.append(QStringLiteral("(none)")); + } else { + for (int t : m_stateTable->array(transition.targets)) + to.append(m_tableData->string(m_stateTable->state(t).name)); + } + qCDebug(qscxmlLog) << q_func() << "\t" << t << ":" << from << "->" + << to.join(QLatin1Char(',')); + } + } + + exitStates(enabledTransitions); + executeTransitionContent(enabledTransitions); + enterStates(enabledTransitions); + + qCDebug(qscxmlLog) << q_func() << "finished microstep, configuration:" + << stateNames(m_configuration.list()); +} + +void QScxmlStateMachinePrivate::exitStates(const OrderedSet &enabledTransitions) +{ + OrderedSet statesToExit; + computeExitSet(enabledTransitions, statesToExit); + auto statesToExitSorted = statesToExit.takeList(); + std::sort(statesToExitSorted.begin(), statesToExitSorted.end(), std::greater<int>()); + qCDebug(qscxmlLog) << q_func() << "exiting states" << stateNames(statesToExitSorted); + for (int s : statesToExitSorted) { + const auto &state = m_stateTable->state(s); + if (state.serviceFactoryIds != StateTable::InvalidIndex) + m_statesToInvoke.remove(s); + } + for (int s : statesToExitSorted) { + for (int h : historyStates(s)) { + const auto &hState = m_stateTable->state(h); + QVector<int> history; + + for (int s0 : m_configuration) { + const auto &s0State = m_stateTable->state(s0); + if (hState.type == StateTable::State::DeepHistory) { + if (s0State.isAtomic() && isDescendant(s0, s)) + history.append(s0); + } else { + if (s0State.parent == s) + history.append(s0); + } + } + + m_historyValue[h] = history; + } + } + for (int s : statesToExitSorted) { + const auto &state = m_stateTable->state(s); + if (state.exitInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(state.exitInstructions); + m_configuration.remove(s); + emitStateActive(s, false); + removeService(s); + } + + if (m_infoSignalProxy) { + emit m_infoSignalProxy->statesExited( + QVector<QScxmlStateMachineInfo::StateId>::fromStdVector(statesToExitSorted)); + } +} + +void QScxmlStateMachinePrivate::computeExitSet(const OrderedSet &enabledTransitions, + OrderedSet &statesToExit) const +{ + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + if (transition.targets == StateTable::InvalidIndex) { + // nothing to do here: there is no exit set + } else { + const int domain = getTransitionDomain(t); + for (int s : m_configuration) { + if (isDescendant(s, domain)) + statesToExit.add(s); + } + } + } +} + +void QScxmlStateMachinePrivate::executeTransitionContent(const OrderedSet &enabledTransitions) +{ + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + if (transition.transitionInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(transition.transitionInstructions); + } + + if (m_infoSignalProxy) { + emit m_infoSignalProxy->transitionsTriggered( + QVector<QScxmlStateMachineInfo::TransitionId>::fromStdVector( + enabledTransitions.list())); + } +} + +void QScxmlStateMachinePrivate::enterStates(const OrderedSet &enabledTransitions) +{ + Q_Q(QScxmlStateMachine); + + OrderedSet statesToEnter, statesForDefaultEntry; + HistoryContent defaultHistoryContent; + computeEntrySet(enabledTransitions, &statesToEnter, &statesForDefaultEntry, + &defaultHistoryContent); + auto sortedStates = statesToEnter.takeList(); + std::sort(sortedStates.begin(), sortedStates.end()); + qCDebug(qscxmlLog) << q_func() << "entering states" << stateNames(sortedStates); + for (int s : sortedStates) { + const auto &state = m_stateTable->state(s); + m_configuration.add(s); + if (state.serviceFactoryIds != StateTable::InvalidIndex) + m_statesToInvoke.insert(s); + if (m_stateTable->binding == StateTable::LateBinding && m_isFirstStateEntry[s]) { + if (state.initInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(state.initInstructions); + m_isFirstStateEntry[s] = false; + } + if (state.entryInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(state.entryInstructions); + if (statesForDefaultEntry.contains(s)) { + const auto &initialTransition = m_stateTable->transition(state.initialTransition); + if (initialTransition.transitionInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(initialTransition.transitionInstructions); + } + const int dhc = defaultHistoryContent.value(s); + if (dhc != StateTable::InvalidIndex) + m_executionEngine->execute(dhc); + if (state.type == StateTable::State::Final) { + if (state.parentIsScxmlElement()) { + m_runningState = Finished; + } else { + const auto &parent = m_stateTable->state(state.parent); + m_executionEngine->execute(state.doneData, m_tableData->string(parent.name)); + if (parent.parent != StateTable::InvalidIndex) { + const auto &grandParent = m_stateTable->state(parent.parent); + if (grandParent.isParallel()) { + if (allInFinalStates(getChildStates(grandParent))) { + auto e = new QScxmlEvent; + e->setEventType(QScxmlEvent::InternalEvent); + e->setName(QStringLiteral("done.state.") + + m_tableData->string(grandParent.name)); + q->submitEvent(e); + } + } + } + } + } + } + for (int s : sortedStates) + emitStateActive(s, true); + if (m_infoSignalProxy) { + emit m_infoSignalProxy->statesEntered( + QVector<QScxmlStateMachineInfo::StateId>::fromStdVector(sortedStates)); + } +} + +void QScxmlStateMachinePrivate::computeEntrySet(const OrderedSet &enabledTransitions, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const +{ + Q_ASSERT(statesToEnter); + Q_ASSERT(statesForDefaultEntry); + Q_ASSERT(defaultHistoryContent); + + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + if (transition.targets == StateTable::InvalidIndex) + // targetless transition, so nothing to do + continue; + for (int s : m_stateTable->array(transition.targets)) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + auto ancestor = getTransitionDomain(t); + OrderedSet targets; + getEffectiveTargetStates(&targets, t); + for (auto s : targets) + addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } +} + +void QScxmlStateMachinePrivate::addDescendantStatesToEnter( + int stateIndex, OrderedSet *statesToEnter, OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const +{ + Q_ASSERT(statesToEnter); + Q_ASSERT(statesForDefaultEntry); + Q_ASSERT(defaultHistoryContent); + + const auto &state = m_stateTable->state(stateIndex); + if (state.isHistoryState()) { + HistoryValues::const_iterator historyValueIter = m_historyValue.find(stateIndex); + if (historyValueIter != m_historyValue.end()) { + auto historyValue = historyValueIter.value(); + for (int s : historyValue) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + for (int s : historyValue) + addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } else { + const auto transitionIdx = m_stateTable->array(state.transitions)[0]; + const auto &defaultHistoryTransition = m_stateTable->transition(transitionIdx); + defaultHistoryContent->operator[](state.parent) = + defaultHistoryTransition.transitionInstructions; + StateTable::Array targetStates = m_stateTable->array(defaultHistoryTransition.targets); + for (int s : targetStates) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + for (int s : targetStates) + addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } + } else { + statesToEnter->add(stateIndex); + if (state.isCompound()) { + statesForDefaultEntry->add(stateIndex); + if (state.initialTransition != StateTable::InvalidIndex) { + auto initialTransition = m_stateTable->transition(state.initialTransition); + auto initialTransitionTargets = m_stateTable->array(initialTransition.targets); + for (int targetStateIndex : initialTransitionTargets) + addDescendantStatesToEnter(targetStateIndex, statesToEnter, + statesForDefaultEntry, defaultHistoryContent); + for (int targetStateIndex : initialTransitionTargets) + addAncestorStatesToEnter(targetStateIndex, stateIndex, statesToEnter, + statesForDefaultEntry, defaultHistoryContent); + } + } else { + if (state.isParallel()) { + for (int child : getChildStates(state)) { + if (!hasDescendant(*statesToEnter, child)) + addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } + } + } + } +} + +void QScxmlStateMachinePrivate::addAncestorStatesToEnter( + int stateIndex, int ancestorIndex, OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, HistoryContent *defaultHistoryContent) const +{ + Q_ASSERT(statesToEnter); + Q_ASSERT(statesForDefaultEntry); + Q_ASSERT(defaultHistoryContent); + + std::vector<int> ancestors; + getProperAncestors(&ancestors, stateIndex, ancestorIndex); + for (int anc : ancestors) { + if (anc == -1) { + // we can't enter the state machine itself, so: + continue; + } + statesToEnter->add(anc); + const auto &ancState = m_stateTable->state(anc); + if (ancState.isParallel()) { + for (int child : getChildStates(ancState)) { + if (!hasDescendant(*statesToEnter, child)) + addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } + } + } +} + +std::vector<int> QScxmlStateMachinePrivate::getChildStates( + const QScxmlExecutableContent::StateTable::State &state) const +{ + std::vector<int> childStates; + auto kids = m_stateTable->array(state.childStates); + if (kids.isValid()) { + childStates.reserve(kids.size()); + for (int kiddo : kids) { + switch (m_stateTable->state(kiddo).type) { + case StateTable::State::Normal: + case StateTable::State::Final: + case StateTable::State::Parallel: + childStates.push_back(kiddo); + break; + default: + break; + } + } + } + return childStates; +} + +bool QScxmlStateMachinePrivate::hasDescendant(const OrderedSet &statesToEnter, int childIdx) const +{ + for (int s : statesToEnter) { + if (isDescendant(s, childIdx)) + return true; + } + return false; +} + +bool QScxmlStateMachinePrivate::allDescendants(const OrderedSet &statesToEnter, int childdx) const +{ + for (int s : statesToEnter) { + if (!isDescendant(s, childdx)) + return false; + } + return true; +} + +bool QScxmlStateMachinePrivate::isDescendant(int state1, int state2) const +{ + int parent = state1; + do { + parent = m_stateTable->state(parent).parent; + if (parent == state2) + return true; + } while (parent != -1); + return false; +} + +bool QScxmlStateMachinePrivate::allInFinalStates(const std::vector<int> &states) const +{ + if (states.empty()) + return false; + + for (int idx : states) { + if (!isInFinalState(idx)) + return false; + } + + return true; +} + +bool QScxmlStateMachinePrivate::someInFinalStates(const std::vector<int> &states) const +{ + for (int stateIndex : states) { + const auto &state = m_stateTable->state(stateIndex); + if (state.type == StateTable::State::Final && m_configuration.contains(stateIndex)) + return true; + } + return false; +} + +bool QScxmlStateMachinePrivate::isInFinalState(int stateIndex) const +{ + const auto &state = m_stateTable->state(stateIndex); + if (state.isCompound()) + return someInFinalStates(getChildStates(state)) && m_configuration.contains(stateIndex); + else if (state.isParallel()) + return allInFinalStates(getChildStates(state)); + else + return false; +} + +int QScxmlStateMachinePrivate::getTransitionDomain(int transitionIndex) const +{ + const auto &transition = m_stateTable->transition(transitionIndex); + if (transition.source == -1) + //oooh, we have the initial transition of the state machine. + return -1; + + OrderedSet tstates; + getEffectiveTargetStates(&tstates, transitionIndex); + if (tstates.isEmpty()) { + return StateTable::InvalidIndex; + } else { + const auto &sourceState = m_stateTable->state(transition.source); + if (transition.type == StateTable::Transition::Internal + && sourceState.isCompound() + && allDescendants(tstates, transition.source)) { + return transition.source; + } else { + tstates.add(transition.source); + return findLCCA(std::move(tstates)); + } + } +} + +int QScxmlStateMachinePrivate::findLCCA(OrderedSet &&states) const +{ + std::vector<int> ancestors; + const int head = *states.begin(); + OrderedSet tail(std::move(states)); + tail.removeHead(); + + getProperAncestors(&ancestors, head, StateTable::InvalidIndex); + for (int anc : ancestors) { + if (anc != -1) { // the state machine itself is always compound + const auto &ancState = m_stateTable->state(anc); + if (!ancState.isCompound()) + continue; + } + + if (allDescendants(tail, anc)) + return anc; + } + + return StateTable::InvalidIndex; +} + +void QScxmlStateMachinePrivate::getEffectiveTargetStates(OrderedSet *targets, + int transitionIndex) const +{ + Q_ASSERT(targets); + + const auto &transition = m_stateTable->transition(transitionIndex); + for (int s : m_stateTable->array(transition.targets)) { + const auto &state = m_stateTable->state(s); + if (state.isHistoryState()) { + HistoryValues::const_iterator historyValueIter = m_historyValue.find(s); + if (historyValueIter != m_historyValue.end()) { + for (int historyState : historyValueIter.value()) { + targets->add(historyState); + } + } else { + getEffectiveTargetStates(targets, m_stateTable->array(state.transitions)[0]); + } + } else { + targets->add(s); + } + } +} + /*! * Creates a state machine from the SCXML file specified by \a fileName. * @@ -403,7 +1401,7 @@ QScxmlStateMachine *QScxmlStateMachine::fromFile(const QString &fileName) { QFile scxmlFile(fileName); if (!scxmlFile.open(QIODevice::ReadOnly)) { - auto stateMachine = new QScxmlStateMachine; + auto stateMachine = new QScxmlStateMachine(&QScxmlStateMachine::staticMetaObject); QScxmlError err(scxmlFile.fileName(), 0, 0, QStringLiteral("cannot open for reading")); QScxmlStateMachinePrivate::get(stateMachine)->parserData()->m_errors.append(err); return stateMachine; @@ -426,35 +1424,29 @@ QScxmlStateMachine *QScxmlStateMachine::fromFile(const QString &fileName) QScxmlStateMachine *QScxmlStateMachine::fromData(QIODevice *data, const QString &fileName) { QXmlStreamReader xmlReader(data); - QScxmlParser parser(&xmlReader); - parser.setFileName(fileName); - parser.parse(); - auto stateMachine = parser.instantiateStateMachine(); - parser.instantiateDataModel(stateMachine); - return stateMachine; + QScxmlCompiler compiler(&xmlReader); + compiler.setFileName(fileName); + return compiler.compile(); } -/*! - * Returns the list of parse errors that occurred while creating a state machine from an - * SCXML file. - */ QVector<QScxmlError> QScxmlStateMachine::parseErrors() const { Q_D(const QScxmlStateMachine); return d->m_parserData ? d->m_parserData->m_errors : QVector<QScxmlError>(); } -QScxmlStateMachine::QScxmlStateMachine(QObject *parent) - : QObject(*new QScxmlStateMachinePrivate, parent) +QScxmlStateMachine::QScxmlStateMachine(const QMetaObject *metaObject, QObject *parent) + : QObject(*new QScxmlStateMachinePrivate(metaObject), parent) { Q_D(QScxmlStateMachine); - d->init(); + d->m_executionEngine = new QScxmlExecutionEngine(this); } QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *parent) : QObject(dd, parent) { - dd.init(); + Q_D(QScxmlStateMachine); + d->m_executionEngine = new QScxmlExecutionEngine(this); } /*! @@ -500,63 +1492,59 @@ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *p */ /*! - \enum QScxmlStateMachine::BindingMethod + \property QScxmlStateMachine::sessionId - This enum specifies the binding method. The binding method controls the point in time - when the initial values are assigned to the data elements. + \brief The session ID of the current state machine. - \value EarlyBinding All data elements are created and initialized at data-model initialization. - This is the default. - \value LateBinding All data elements are created at initialization, but the initial values are - assigned only when the containing state is entered for the first time. This is done - before any executable content is executed. + The session ID is used for message routing between parent and child state machines. If a state + machine is started by an \c <invoke> element, any event it sends will have the \c invokeid field + set to the session ID. The state machine will use the origin of an event (which is set by the + \e target or \e targetexpr attribute in a \c <send> element) to dispatch messages to the correct + child state machine. + + \sa QScxmlEvent::invokeId() */ /*! - * Returns the session ID for the current state machine. - * - * The session ID is used for message routing between parent and child state machines. If a state - * machine is started by an \c <invoke> element, any event it sends will have the \c invokeid field - * set to the session ID. The state machine will use the origin of an event (which is set by the - * \e target or \e targetexpr attribute in a \c <send> element) to dispatch messages to the correct - * child state machine. - * - * \sa setSessionId() QScxmlEvent::invokeId() + \property QScxmlStateMachine::name + + \brief The name of the state machine as set by the \e name attribute of the \c <scxml> tag. */ -QString QScxmlStateMachine::sessionId() const -{ - Q_D(const QScxmlStateMachine); - return d->m_sessionId; -} +/*! + \property QScxmlStateMachine::invoked + + \brief Whether the state machine was invoked from an outer state machine. + + \c true when the state machine was started as a service with the \c <invoke> element, + \c false otherwise. + */ /*! - Sets the session ID for the current state machine to \a id. + \property QScxmlStateMachine::parseErrors - \sa sessionId() + \brief The list of parse errors that occurred while creating a state machine from an SCXML file. */ -void QScxmlStateMachine::setSessionId(const QString &id) -{ - Q_D(QScxmlStateMachine); - d->m_sessionId = id; -} /*! - * Generates a unique ID by appending a unique number to the \a prefix. - * - * The number is only unique within a single run of an application. This method is used when an - * invoked service does not have an ID set (the \e id attribute in \c <invoke>). + \property QScxmlStateMachine::loader + + \brief The loader that is currently used to resolve and load URIs for the state machine. */ -QString QScxmlStateMachine::generateSessionId(const QString &prefix) + +QString QScxmlStateMachine::sessionId() const +{ + Q_D(const QScxmlStateMachine); + + return d->m_sessionId; +} + +QString QScxmlStateMachinePrivate::generateSessionId(const QString &prefix) { int id = ++QScxmlStateMachinePrivate::m_sessionIdCounter; return prefix + QString::number(id); } -/*! - * Returns \c true when the state machine was started as a service with the \c <invoke> element, - * \c false otherwise. - */ bool QScxmlStateMachine::isInvoked() const { Q_D(const QScxmlStateMachine); @@ -597,25 +1585,21 @@ QScxmlDataModel *QScxmlStateMachine::dataModel() const return d->m_dataModel; } -/*! - * \internal - * Sets the binding method to the specified value. - */ -void QScxmlStateMachine::setDataBinding(QScxmlStateMachine::BindingMethod bindingMethod) +void QScxmlStateMachine::setLoader(QScxmlCompiler::Loader *loader) { Q_D(QScxmlStateMachine); - d->m_dataBinding = bindingMethod; + if (loader != d->m_loader) { + d->m_loader = loader; + emit loaderChanged(loader); + } } -/*! - * Returns the binding method used by the state machine. - */ -QScxmlStateMachine::BindingMethod QScxmlStateMachine::dataBinding() const +QScxmlCompiler::Loader *QScxmlStateMachine::loader() const { Q_D(const QScxmlStateMachine); - return d->m_dataBinding; + return d->m_loader; } /*! @@ -645,271 +1629,23 @@ void QScxmlStateMachine::setTableData(QScxmlTableData *tableData) Q_ASSERT(tableData); d->m_tableData = tableData; + d->m_stateTable = reinterpret_cast<const QScxmlExecutableContent::StateTable *>( + tableData->stateMachineTable()); if (objectName().isEmpty()) { setObjectName(tableData->name()); } -} - -void QScxmlInternal::WrappedQStateMachine::beginSelectTransitions(QEvent *event) -{ - Q_D(WrappedQStateMachine); - - if (event && event->type() == QScxmlEvent::scxmlEventType) { - stateMachinePrivate()->m_event = *static_cast<QScxmlEvent *>(event); - d->stateMachine()->dataModel()->setScxmlEvent(stateMachinePrivate()->m_event); - - auto scxmlEvent = static_cast<QScxmlEvent *>(event); - auto smp = stateMachinePrivate(); - - foreach (QScxmlInvokableService *service, smp->invokedServices()) { - if (scxmlEvent->invokeId() == service->id()) { - service->finalize(); - } - if (service->autoforward()) { - qCDebug(qscxmlLog) << this << "auto-forwarding event" << scxmlEvent->name() - << "from" << stateMachine()->name() << "to service" << service->id(); - service->postEvent(new QScxmlEvent(*scxmlEvent)); - } - } - - if (scxmlEvent->eventType() == QScxmlEvent::ExternalEvent) { - emit d->stateMachine()->eventOccurred(*scxmlEvent); - } - - if (scxmlEvent->originType() == QLatin1String("qt:signal")) { - emit d->stateMachine()->externalEventOccurred(*scxmlEvent); - } - - if (smp->m_eventFilter && !smp->m_eventFilter->handle(scxmlEvent, d->stateMachine())) { - scxmlEvent->makeIgnorable(); - scxmlEvent->clear(); - smp->m_event.clear(); - return; - } - } else { - stateMachinePrivate()->m_event.clear(); - d->stateMachine()->dataModel()->setScxmlEvent(stateMachinePrivate()->m_event); - } -} - -void QScxmlInternal::WrappedQStateMachine::beginMicrostep(QEvent *event) -{ - Q_D(WrappedQStateMachine); - - qCDebug(qscxmlLog) << d->m_stateMachine - << "started microstep from state" << d->m_stateMachine->activeStateNames() - << "with event" << stateMachinePrivate()->m_event.name() - << "and event type" << event->type(); -} - -void QScxmlInternal::WrappedQStateMachine::endMicrostep(QEvent *event) -{ - Q_D(WrappedQStateMachine); - Q_UNUSED(event); - - qCDebug(qscxmlLog) << d->m_stateMachine - << "finished microstep in state (" << d->m_stateMachine->activeStateNames() << ")"; -} - -// This is a slightly modified copy of QStateMachinePrivate::event() -// Instead of postExternalEvent and processEvents -// we route event first to the appropriate state machine instance. -bool QScxmlInternal::WrappedQStateMachine::event(QEvent *e) -{ - Q_D(QScxmlInternal::WrappedQStateMachine); - if (e->type() == QEvent::Timer) { - QTimerEvent *te = static_cast<QTimerEvent*>(e); - int tid = te->timerId(); - if (d->state != QStateMachinePrivate::Running) { - // This event has been cancelled already - QMutexLocker locker(&d->delayedEventsMutex); - Q_ASSERT(!d->timerIdToDelayedEventId.contains(tid)); - return true; - } - d->delayedEventsMutex.lock(); - int id = d->timerIdToDelayedEventId.take(tid); - QStateMachinePrivate::DelayedEvent ee = d->delayedEvents.take(id); - if (ee.event != 0) { - Q_ASSERT(ee.timerId == tid); -// killTimer(tid); -// d->delayedEventIdFreeList.release(id); - d->delayedEventsMutex.unlock(); - d->_q_killDelayedEventTimer(id, tid); - // route here - if (ee.event->type() == QScxmlEvent::scxmlEventType) - QScxmlStateMachinePrivate::get(stateMachine())->routeEvent(static_cast<QScxmlEvent *>(ee.event)); -// d->postExternalEvent(ee.event); -// d->processEvents(QStateMachinePrivate::DirectProcessing); - return true; - } else { - d->delayedEventsMutex.unlock(); - } - } - return QState::event(e); -} - -void QScxmlInternal::WrappedQStateMachinePrivate::noMicrostep() -{ - qCDebug(qscxmlLog) << m_stateMachine - << "had no transition, stays in state (" << m_stateMachine->activeStateNames() << ")"; -} - -void QScxmlInternal::WrappedQStateMachinePrivate::processedPendingEvents(bool didChange) -{ - qCDebug(qscxmlLog) << m_stateMachine << "finishedPendingEvents" << didChange << "in state (" - << m_stateMachine->activeStateNames() << ")"; - emit m_stateMachine->reachedStableState(); -} - -void QScxmlInternal::WrappedQStateMachinePrivate::beginMacrostep() -{ -} - -void QScxmlInternal::WrappedQStateMachinePrivate::endMacrostep(bool didChange) -{ - qCDebug(qscxmlLog) << m_stateMachine << "endMacrostep" << didChange - << "in state (" << m_stateMachine->activeStateNames() << ")"; - - { // handle <invoke>s - QVector<QScxmlState*> &sti = stateMachinePrivate()->m_statesToInvoke; - std::sort(sti.begin(), sti.end(), WrappedQStateMachinePrivate::stateEntryLessThan); - foreach (QScxmlState *s, sti) { - auto sp = QScxmlStatePrivate::get(s); - foreach (QScxmlInvokableService *s, sp->servicesWaitingToStart) { - s->start(); - } - sp->servicesWaitingToStart.clear(); - } - sti.clear(); - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::enterStates( - QEvent *event, - const QList<QAbstractState*> &exitedStates_sorted, - const QList<QAbstractState*> &statesToEnter_sorted, - const QSet<QAbstractState*> &statesForDefaultEntry, - QHash<QAbstractState *, QVector<QPropertyAssignment> > &propertyAssignmentsForState -# ifndef QT_NO_ANIMATION - , const QList<QAbstractAnimation*> &selectedAnimations -# endif - ) -{ - QStateMachinePrivate::enterStates(event, exitedStates_sorted, statesToEnter_sorted, - statesForDefaultEntry, propertyAssignmentsForState -# ifndef QT_NO_ANIMATION - , selectedAnimations -# endif - ); - foreach (QAbstractState *s, statesToEnter_sorted) { - if (QScxmlState *qss = qobject_cast<QScxmlState *>(s)) { - if (!QScxmlStatePrivate::get(qss)->invokableServiceFactories.isEmpty()) { - if (!stateMachinePrivate()->m_statesToInvoke.contains(qss)) { - stateMachinePrivate()->m_statesToInvoke.append(qss); - } - } - } - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::exitStates( - QEvent *event, - const QList<QAbstractState *> &statesToExit_sorted, - const QHash<QAbstractState*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) -{ - QStateMachinePrivate::exitStates(event, statesToExit_sorted, assignmentsForEnteredStates); - - auto smp = stateMachinePrivate(); - for (int i = 0; i < smp->m_statesToInvoke.size(); ) { - if (statesToExit_sorted.contains(smp->m_statesToInvoke.at(i))) { - smp->m_statesToInvoke.removeAt(i); - } else { - ++i; - } - } - - foreach (QAbstractState *s, statesToExit_sorted) { - if (QScxmlState *qss = qobject_cast<QScxmlState *>(s)) { - auto ssp = QScxmlStatePrivate::get(qss); - ssp->servicesWaitingToStart.clear(); - QVector<QScxmlInvokableService *> &services = ssp->invokedServices; - foreach (QScxmlInvokableService *service, services) { - qCDebug(qscxmlLog) << stateMachine() << "schedule service cancellation" << service->id(); - QMetaObject::invokeMethod(q_func(), - "removeAndDestroyService", - Qt::QueuedConnection, - Q_ARG(QScxmlInvokableService *,service)); - } - services.clear(); - } - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::exitInterpreter() -{ - Q_Q(WrappedQStateMachine); - - foreach (QAbstractState *s, configuration) { - QScxmlExecutableContent::ContainerId onExitInstructions = QScxmlExecutableContent::NoInstruction; - if (QScxmlFinalState *finalState = qobject_cast<QScxmlFinalState *>(s)) { - stateMachinePrivate()->m_executionEngine->execute(finalState->doneData(), QVariant()); - onExitInstructions = QScxmlFinalStatePrivate::get(finalState)->onExitInstructions; - } else if (QScxmlState *state = qobject_cast<QScxmlState *>(s)) { - onExitInstructions = QScxmlStatePrivate::get(state)->onExitInstructions; - } - - if (onExitInstructions != QScxmlExecutableContent::NoInstruction) { - stateMachinePrivate()->m_executionEngine->execute(onExitInstructions); - } - - if (QScxmlFinalState *finalState = qobject_cast<QScxmlFinalState *>(s)) { - if (finalState->parent() == q) { - if (auto psm = stateMachinePrivate()->m_parentStateMachine) { - auto done = new QScxmlEvent; - done->setName(QStringLiteral("done.invoke.") + m_stateMachine->sessionId()); - done->setInvokeId(m_stateMachine->sessionId()); - qCDebug(qscxmlLog) << "submitting event" << done->name() << "to" << psm->name(); - psm->submitEvent(done); - } - } - } - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState) -{ - Q_Q(WrappedQStateMachine); - - if (QScxmlFinalState *finalState = qobject_cast<QScxmlFinalState *>(guiltyState)) { - if (!q->isRunning()) - return; - stateMachinePrivate()->m_executionEngine->execute(finalState->doneData(), forState->objectName()); - } - - QStateMachinePrivate::emitStateFinished(forState, guiltyState); -} - -void QScxmlInternal::WrappedQStateMachinePrivate::startupHook() -{ - Q_Q(WrappedQStateMachine); - - q->submitQueuedEvents(); -} - -int QScxmlInternal::WrappedQStateMachinePrivate::eventIdForDelayedEvent(const QString &sendId) -{ - QMutexLocker locker(&delayedEventsMutex); - - QHash<int, DelayedEvent>::const_iterator it; - for (it = delayedEvents.constBegin(); it != delayedEvents.constEnd(); ++it) { - if (QScxmlEvent *e = dynamic_cast<QScxmlEvent *>(it->event)) { - if (e->sendId() == sendId) { - return it.key(); - } - } + if (d->m_stateTable->maxServiceId != QScxmlExecutableContent::StateTable::InvalidIndex) { + const size_t serviceCount = size_t(d->m_stateTable->maxServiceId + 1); + d->m_invokedServices.resize(serviceCount, { -1, nullptr, QString() }); + d->m_cachedFactories.resize(serviceCount, nullptr); } - return -1; + if (d->m_stateTable->version != Q_QSCXMLC_OUTPUT_REVISION) + qFatal("Cannot mix incompatible state table (version 0x%x) with this library (version 0x%x)", + d->m_stateTable->version, Q_QSCXMLC_OUTPUT_REVISION); + Q_ASSERT(tableData->stateMachineTable()[d->m_stateTable->arrayOffset + + d->m_stateTable->arraySize] + == QScxmlExecutableContent::StateTable::terminator); } /*! @@ -920,26 +1656,21 @@ int QScxmlInternal::WrappedQStateMachinePrivate::eventIdForDelayedEvent(const QS * When it is \c false, the full list of all states will be returned. * * The returned list does not contain the states of possible nested state machines. + * + * \note The order of the state names in the list is the order in which the states occurred in + * the SCXML document. */ QStringList QScxmlStateMachine::stateNames(bool compress) const { Q_D(const QScxmlStateMachine); - QList<QObject *> worklist; - worklist.reserve(d->m_qStateMachine->children().size() + d->m_qStateMachine->configuration().size()); - worklist.append(d->m_qStateMachine->children()); - - QStringList res; - while (!worklist.isEmpty()) { - QObject *obj = worklist.takeLast(); - if (QAbstractState *state = qobject_cast<QAbstractState *>(obj)) { - if (!compress || !obj->children().count()) - res.append(state->objectName()); - worklist.append(obj->children()); - } + QStringList names; + for (int i = 0; i < d->m_stateTable->stateCount; ++i) { + const auto &state = d->m_stateTable->state(i); + if (!compress || state.isAtomic()) + names.append(d->m_tableData->string(state.name)); } - std::sort(res.begin(), res.end()); - return res; + return names; } /*! @@ -953,19 +1684,13 @@ QStringList QScxmlStateMachine::activeStateNames(bool compress) const { Q_D(const QScxmlStateMachine); - QSet<QAbstractState *> config = QStateMachinePrivate::get(d->m_qStateMachine)->configuration; - if (compress) - foreach (const QAbstractState *s, config) - config.remove(s->parentState()); - QStringList res; - foreach (const QAbstractState *s, config) { - QString id = s->objectName(); - if (!id.isEmpty()) { - res.append(id); - } + QStringList result; + for (int stateIdx : d->m_configuration) { + const auto &state = d->m_stateTable->state(stateIdx); + if (state.isAtomic() || !compress) + result.append(d->m_tableData->string(state.name)); } - std::sort(res.begin(), res.end()); - return res; + return result; } /*! @@ -974,48 +1699,89 @@ QStringList QScxmlStateMachine::activeStateNames(bool compress) const bool QScxmlStateMachine::isActive(const QString &scxmlStateName) const { Q_D(const QScxmlStateMachine); - QSet<QAbstractState *> config = QStateMachinePrivate::get(d->m_qStateMachine)->configuration; - foreach (QAbstractState *s, config) { - if (s->objectName() == scxmlStateName) { + + for (int stateIndex : d->m_configuration) { + const auto &state = d->m_stateTable->state(stateIndex); + if (d->m_tableData->string(state.name) == scxmlStateName) return true; - } } + return false; } -/*! - * Creates a connection of the given \a type from the state identified by \a scxmlStateName - * to the \a method in the \a receiver object. The receiver's \a method - * may contain a boolean argument that indicates whether the state connected - * became active or inactive. - * - * Returns a handle to the connection, which can be used later to disconnect. - */ -QMetaObject::Connection QScxmlStateMachine::connectToState(const QString &scxmlStateName, - const QObject *receiver, const char *method, - Qt::ConnectionType type) +QMetaObject::Connection QScxmlStateMachine::connectToStateImpl(const QString &scxmlStateName, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type) { + const int *types = Q_NULLPTR; + if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) + types = QtPrivate::ConnectionTypes<QtPrivate::List<bool> >::types(); + Q_D(QScxmlStateMachine); - QAbstractState *state = findState(scxmlStateName, d->m_qStateMachine); - return QObject::connect(state, SIGNAL(activeChanged(bool)), receiver, method, type); + int signalIndex = QScxmlInternal::signalIndex(d->m_metaObject, + scxmlStateName.toUtf8() + "Changed(bool)"); + return signalIndex < 0 ? QMetaObject::Connection() + : QObjectPrivate::connectImpl(this, signalIndex, receiver, slot, slotObj, + type, types, d->m_metaObject); } /*! - * Returns the SCXML event filter if one is set, otherwise returns null. + Creates a connection of the given \a type from the state identified by \a scxmlStateName + to the \a method in the \a receiver object. The receiver's \a method + may take a boolean argument that indicates whether the state connected + became active or inactive. For example: + + \code + void mySlot(bool active); + \endcode + + Returns a handle to the connection, which can be used later to disconnect. */ -QScxmlEventFilter *QScxmlStateMachine::scxmlEventFilter() const +QMetaObject::Connection QScxmlStateMachine::connectToState(const QString &scxmlStateName, + const QObject *receiver, + const char *method, + Qt::ConnectionType type) { - Q_D(const QScxmlStateMachine); - return d->m_eventFilter; + QByteArray signalName = QByteArray::number(QSIGNAL_CODE) + scxmlStateName.toUtf8() + + "Changed(bool)"; + return QObject::connect(this, signalName.constData(), receiver, method, type); } /*! - * Sets the \a newFilter as the SCXML event filter. Passing null will remove the current filter. - */ -void QScxmlStateMachine::setScxmlEventFilter(QScxmlEventFilter *newFilter) + Creates a connection of the specified \a type from the event specified by + \a scxmlEventSpec to the \a method in the \a receiver object. The receiver's + \a method may take a QScxmlEvent as a parameter. For example: + + \code + void mySlot(const QScxmlEvent &event); + \endcode + + In contrast to event specifications in SCXML documents, spaces are not + allowed in the \a scxmlEventSpec here. In order to connect to multiple + events with different prefixes, connectToEvent() has to be called multiple + times. + + Returns a handle to the connection, which can be used later to disconnect. +*/ +QMetaObject::Connection QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec, + const QObject *receiver, + const char *method, + Qt::ConnectionType type) +{ + Q_D(QScxmlStateMachine); + return d->m_router.connectToEvent(scxmlEventSpec.split(QLatin1Char('.')), receiver, method, + type); +} + +QMetaObject::Connection QScxmlStateMachine::connectToEventImpl(const QString &scxmlEventSpec, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type) { Q_D(QScxmlStateMachine); - d->m_eventFilter = newFilter; + return d->m_router.connectToEvent(scxmlEventSpec.split(QLatin1Char('.')), receiver, slot, + slotObj, type); } /*! @@ -1058,7 +1824,7 @@ bool QScxmlStateMachine::isRunning() const { Q_D(const QScxmlStateMachine); - return d->m_qStateMachine->isRunning(); + return d->isRunnable() && !d->isPaused(); } /*! @@ -1089,9 +1855,6 @@ void QScxmlStateMachine::setInitialValues(const QVariantMap &initialValues) } } -/*! - * Returns the name of the state machine as set by the \e name attribute of the \c <scxml> tag. - */ QString QScxmlStateMachine::name() const { return tableData()->name(); @@ -1116,9 +1879,7 @@ void QScxmlStateMachine::submitEvent(QScxmlEvent *event) << QScxmlEventPrivate::debugString(event).constData(); Q_ASSERT(event->eventType() == QScxmlEvent::ExternalEvent); - int id = d->m_qStateMachine->postDelayedEvent(event, event->delay()); - - qCDebug(qscxmlLog) << this << ": delayed event" << event->name() << "(" << event << ") got id:" << id; + d->submitDelayedEvent(event); } else { qCDebug(qscxmlLog) << this << "submitting event" << event->name() << ":" << QScxmlEventPrivate::debugString(event).constData(); @@ -1146,9 +1907,8 @@ void QScxmlStateMachine::submitEvent(const QString &eventName) void QScxmlStateMachine::submitEvent(const QString &eventName, const QVariant &data) { QVariant incomingData = data; - if (incomingData.canConvert<QJSValue>()) { + if (incomingData.canConvert<QJSValue>()) incomingData = incomingData.value<QJSValue>().toVariant(); - } QScxmlEvent *e = new QScxmlEvent; e->setName(eventName); @@ -1164,49 +1924,16 @@ void QScxmlStateMachine::cancelDelayedEvent(const QString &sendId) { Q_D(QScxmlStateMachine); - int id = d->m_qStateMachine->eventIdForDelayedEvent(sendId); - - qCDebug(qscxmlLog) << this << "canceling event" << sendId << "with id" << id; - - if (id != -1) - d->m_qStateMachine->cancelDelayedEvent(id); -} - -void QScxmlInternal::WrappedQStateMachine::queueEvent(QScxmlEvent *event, EventPriority priority) -{ - Q_D(WrappedQStateMachine); - - if (!d->m_queuedEvents) - d->m_queuedEvents = new QVector<WrappedQStateMachinePrivate::QueuedEvent>(); - d->m_queuedEvents->append(WrappedQStateMachinePrivate::QueuedEvent(event, priority)); -} - -void QScxmlInternal::WrappedQStateMachine::submitQueuedEvents() -{ - Q_D(WrappedQStateMachine); - - qCDebug(qscxmlLog) << d->m_stateMachine << ": submitting queued events"; - - if (d->m_queuedEvents) { - foreach (const WrappedQStateMachinePrivate::QueuedEvent &e, *d->m_queuedEvents) - postEvent(e.event, e.priority); - delete d->m_queuedEvents; - d->m_queuedEvents = Q_NULLPTR; - } -} - -int QScxmlInternal::WrappedQStateMachine::eventIdForDelayedEvent(const QString &sendId) -{ - Q_D(WrappedQStateMachine); - return d->eventIdForDelayedEvent(sendId); -} - -void QScxmlInternal::WrappedQStateMachine::removeAndDestroyService(QScxmlInvokableService *service) -{ - Q_D(WrappedQStateMachine); - qCDebug(qscxmlLog) << stateMachine() << "canceling service" << service->id(); - if (d->stateMachinePrivate()->removeService(service)) { - delete service; + for (auto it = d->m_delayedEvents.begin(), eit = d->m_delayedEvents.end(); it != eit; ++it) { + if (it->second->sendId() == sendId) { + qCDebug(qscxmlLog) << this + << "canceling event" << sendId + << "with timer id" << it->first; + d->m_eventLoopHook.killTimer(it->first); + delete it->second; + d->m_delayedEvents.erase(it); + return; + } } } @@ -1235,10 +1962,9 @@ bool QScxmlStateMachine::isDispatchableTarget(const QString &target) const if (target.startsWith(QStringLiteral("#_"))) { QStringRef targetId = target.midRef(2); - foreach (QScxmlInvokableService *service, d->m_invokedServices) { - if (service->id() == targetId) { + for (auto invokedService : d->m_invokedServices) { + if (invokedService.service->id() == targetId) return true; - } } } @@ -1246,6 +1972,24 @@ bool QScxmlStateMachine::isDispatchableTarget(const QString &target) const } /*! + \property QScxmlStateMachine::invokedServices + \brief A list of SCXML services that were invoked from the main + state machine (possibly recursively). +*/ + +QVector<QScxmlInvokableService *> QScxmlStateMachine::invokedServices() const +{ + Q_D(const QScxmlStateMachine); + + QVector<QScxmlInvokableService *> result; + for (int i = 0, ei = int(d->m_invokedServices.size()); i != ei; ++i) { + if (auto service = d->m_invokedServices[size_t(i)].service) + result.append(service); + } + return result; +} + +/*! \fn QScxmlStateMachine::runningChanged(bool running) This signal is emitted when the \c running property is changed with \a running as argument. @@ -1297,7 +2041,8 @@ void QScxmlStateMachine::start() if (!isInitialized() && !init()) qCDebug(qscxmlLog) << this << "cannot be initialized on start(). Starting anyway ..."; - d->m_qStateMachine->start(); + d->start(); + d->m_eventLoopHook.queueProcessEvents(); } /*! @@ -1309,16 +2054,13 @@ void QScxmlStateMachine::start() void QScxmlStateMachine::stop() { Q_D(QScxmlStateMachine); - d->m_qStateMachine->stop(); + d->pause(); } -/*! - * \internal - */ -void QScxmlStateMachine::setService(const QString &id, QScxmlInvokableService *service) +bool QScxmlStateMachine::isActive(int stateIndex) const { - Q_UNUSED(id); - Q_UNUSED(service); + Q_D(const QScxmlStateMachine); + return d->m_configuration.contains(stateIndex); } QT_END_NAMESPACE diff --git a/src/scxml/qscxmlstatemachine.h b/src/scxml/qscxmlstatemachine.h index 5a800d5..2dea079 100644 --- a/src/scxml/qscxmlstatemachine.h +++ b/src/scxml/qscxmlstatemachine.h @@ -44,60 +44,50 @@ #include <QtScxml/qscxmlexecutablecontent.h> #include <QtScxml/qscxmlerror.h> #include <QtScxml/qscxmlevent.h> +#include <QtScxml/qscxmlcompiler.h> #include <QString> #include <QVector> #include <QUrl> #include <QVariantList> +#include <QPointer> + +#include <functional> QT_BEGIN_NAMESPACE class QIODevice; class QXmlStreamWriter; class QTextStream; -class QScxmlEventBuilder; -class QScxmlInvokableServiceFactory; class QScxmlInvokableService; -class QScxmlParser; -class QScxmlStateMachine; -class QScxmlTableData; - -class Q_SCXML_EXPORT QScxmlEventFilter -{ -public: - virtual ~QScxmlEventFilter(); - virtual bool handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) = 0; -}; class QScxmlStateMachinePrivate; class Q_SCXML_EXPORT QScxmlStateMachine: public QObject { Q_DECLARE_PRIVATE(QScxmlStateMachine) Q_OBJECT - Q_ENUMS(BindingMethod) Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) Q_PROPERTY(bool initialized READ isInitialized NOTIFY initializedChanged) Q_PROPERTY(QScxmlDataModel *dataModel READ dataModel WRITE setDataModel NOTIFY dataModelChanged) Q_PROPERTY(QVariantMap initialValues READ initialValues WRITE setInitialValues NOTIFY initialValuesChanged) + Q_PROPERTY(QVector<QScxmlInvokableService *> invokedServices READ invokedServices NOTIFY invokedServicesChanged) + Q_PROPERTY(QString sessionId READ sessionId CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(bool invoked READ isInvoked CONSTANT) + Q_PROPERTY(QVector<QScxmlError> parseErrors READ parseErrors CONSTANT) + Q_PROPERTY(QScxmlCompiler::Loader *loader READ loader WRITE setLoader NOTIFY loaderChanged) protected: #ifndef Q_QDOC - explicit QScxmlStateMachine(QObject *parent = nullptr); - QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *parent); + explicit QScxmlStateMachine(const QMetaObject *metaObject, QObject *parent = nullptr); + QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *parent = nullptr); #endif // Q_QDOC public: - enum BindingMethod { - EarlyBinding, - LateBinding - }; - static QScxmlStateMachine *fromFile(const QString &fileName); static QScxmlStateMachine *fromData(QIODevice *data, const QString &fileName = QString()); QVector<QScxmlError> parseErrors() const; QString sessionId() const; - void setSessionId(const QString &id); - static QString generateSessionId(const QString &prefix); bool isInvoked() const; bool isInitialized() const; @@ -105,7 +95,8 @@ public: void setDataModel(QScxmlDataModel *model); QScxmlDataModel *dataModel() const; - BindingMethod dataBinding() const; + void setLoader(QScxmlCompiler::Loader *loader); + QScxmlCompiler::Loader *loader() const; bool isRunning() const; void setRunning(bool running); @@ -114,34 +105,225 @@ public: void setInitialValues(const QVariantMap &initialValues); QString name() const; - QStringList stateNames(bool compress = true) const; - QStringList activeStateNames(bool compress = true) const; - bool isActive(const QString &scxmlStateName) const; + Q_INVOKABLE QStringList stateNames(bool compress = true) const; + Q_INVOKABLE QStringList activeStateNames(bool compress = true) const; + Q_INVOKABLE bool isActive(const QString &scxmlStateName) const; QMetaObject::Connection connectToState(const QString &scxmlStateName, - const QObject *receiver, const char *method, - Qt::ConnectionType type = Qt::AutoConnection); + const QObject *receiver, const char *method, + Qt::ConnectionType type = Qt::AutoConnection); +#ifdef Q_QDOC + template<typename PointerToMemberFunction> + QMetaObject::Connection connectToState(const QString &scxmlStateName, + const QObject *receiver, PointerToMemberFunction method, + Qt::ConnectionType type = Qt::AutoConnection); + template<typename Functor> + QMetaObject::Connection connectToState(const QString &scxmlStateName, Functor functor, + Qt::ConnectionType type = Qt::AutoConnection); + template<typename Functor> + QMetaObject::Connection connectToState(const QString &scxmlStateName, + const QObject *context, Functor functor, + Qt::ConnectionType type = Qt::AutoConnection); +#else + + // connect state to a QObject slot + template <typename Func1> + inline QMetaObject::Connection connectToState( + const QString &scxmlStateName, + const typename QtPrivate::FunctionPointer<Func1>::Object *receiver, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + typedef QtPrivate::FunctionPointer<Func1> SlotType; + return connectToStateImpl( + scxmlStateName, receiver, nullptr, + new QtPrivate::QSlotObject<Func1, typename SlotType::Arguments, void>(slot), + type); + } + + // connect state to a functor or function pointer (without context) + template <typename Func1> + inline typename QtPrivate::QEnableIf< + !QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction && + !std::is_same<const char*, Func1>::value, QMetaObject::Connection>::Type + connectToState(const QString &scxmlStateName, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + // Use this as context + return connectToState(scxmlStateName, this, slot, type); + } + + // connectToState to a functor or function pointer (with context) + template <typename Func1> + inline typename QtPrivate::QEnableIf< + !QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction && + !std::is_same<const char*, Func1>::value, QMetaObject::Connection>::Type + connectToState(const QString &scxmlStateName, QObject *context, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + QtPrivate::QSlotObjectBase *slotObj = new QtPrivate::QFunctorSlotObject<Func1, 1, + QtPrivate::List<bool>, void>(slot); + return connectToStateImpl(scxmlStateName, context, reinterpret_cast<void **>(&slot), + slotObj, type); + } +#endif + +#ifdef Q_QDOC + static std::function<void(bool)> onEntry(const QObject *receiver, const char *method); + static std::function<void(bool)> onExit(const QObject *receiver, const char *method); + + template<typename Functor> + static std::function<void(bool)> onEntry(Functor functor); + + template<typename Functor> + static std::function<void(bool)> onExit(Functor functor); - QScxmlEventFilter *scxmlEventFilter() const; - void setScxmlEventFilter(QScxmlEventFilter *newFilter); + template<typename PointerToMemberFunction> + static std::function<void(bool)> onEntry(const QObject *receiver, + PointerToMemberFunction method); + + template<typename PointerToMemberFunction> + static std::function<void(bool)> onExit(const QObject *receiver, + PointerToMemberFunction method); +#else + static std::function<void(bool)> onEntry(const QObject *receiver, const char *method) + { + const QPointer<QObject> receiverPointer(const_cast<QObject *>(receiver)); + return [receiverPointer, method](bool isEnteringState) { + if (isEnteringState && !receiverPointer.isNull()) + QMetaObject::invokeMethod(const_cast<QObject *>(receiverPointer.data()), method); + }; + } + + static std::function<void(bool)> onExit(const QObject *receiver, const char *method) + { + const QPointer<QObject> receiverPointer(const_cast<QObject *>(receiver)); + return [receiverPointer, method](bool isEnteringState) { + if (!isEnteringState && !receiverPointer.isNull()) + QMetaObject::invokeMethod(receiverPointer.data(), method); + }; + } + + template<typename Functor> + static std::function<void(bool)> onEntry(Functor functor) + { + return [functor](bool isEnteringState) { + if (isEnteringState) + functor(); + }; + } + + template<typename Functor> + static std::function<void(bool)> onExit(Functor functor) + { + return [functor](bool isEnteringState) { + if (!isEnteringState) + functor(); + }; + } + + template<typename Func1> + static std::function<void(bool)> onEntry( + const typename QtPrivate::FunctionPointer<Func1>::Object *receiver, Func1 slot) + { + typedef typename QtPrivate::FunctionPointer<Func1>::Object Object; + const QPointer<Object> receiverPointer(const_cast<Object *>(receiver)); + return [receiverPointer, slot](bool isEnteringState) { + if (isEnteringState && !receiverPointer.isNull()) + (receiverPointer->*slot)(); + }; + } + + template<typename Func1> + static std::function<void(bool)> onExit( + const typename QtPrivate::FunctionPointer<Func1>::Object *receiver, Func1 slot) + { + typedef typename QtPrivate::FunctionPointer<Func1>::Object Object; + const QPointer<Object> receiverPointer(const_cast<Object *>(receiver)); + return [receiverPointer, slot](bool isEnteringState) { + if (!isEnteringState && !receiverPointer.isNull()) + (receiverPointer->*slot)(); + }; + } +#endif // !Q_QDOC + + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, + const QObject *receiver, const char *method, + Qt::ConnectionType type = Qt::AutoConnection); + +#ifdef Q_QDOC + template<typename PointerToMemberFunction> + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, + const QObject *receiver, PointerToMemberFunction method, + Qt::ConnectionType type = Qt::AutoConnection); + template<typename Functor> + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, Functor functor, + Qt::ConnectionType type = Qt::AutoConnection); + template<typename Functor> + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, + const QObject *context, Functor functor, + Qt::ConnectionType type = Qt::AutoConnection); +#else + + // connect state to a QObject slot + template <typename Func1> + inline QMetaObject::Connection connectToEvent( + const QString &scxmlEventSpec, + const typename QtPrivate::FunctionPointer<Func1>::Object *receiver, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + typedef QtPrivate::FunctionPointer<Func1> SlotType; + return connectToEventImpl( + scxmlEventSpec, receiver, nullptr, + new QtPrivate::QSlotObject<Func1, typename SlotType::Arguments, void>(slot), + type); + } + + // connect state to a functor or function pointer (without context) + template <typename Func1> + inline typename QtPrivate::QEnableIf< + !QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction && + !std::is_same<const char*, Func1>::value, QMetaObject::Connection>::Type + connectToEvent(const QString &scxmlEventSpec, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + // Use this as context + return connectToEvent(scxmlEventSpec, this, slot, type); + } + + // connectToEvent to a functor or function pointer (with context) + template <typename Func1> + inline typename QtPrivate::QEnableIf< + !QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction && + !std::is_same<const char*, Func1>::value, QMetaObject::Connection>::Type + connectToEvent(const QString &scxmlEventSpec, QObject *context, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + QtPrivate::QSlotObjectBase *slotObj = new QtPrivate::QFunctorSlotObject<Func1, 1, + QtPrivate::List<QScxmlEvent>, void>(slot); + return connectToEventImpl(scxmlEventSpec, context, reinterpret_cast<void **>(&slot), + slotObj, type); + } +#endif Q_INVOKABLE void submitEvent(QScxmlEvent *event); Q_INVOKABLE void submitEvent(const QString &eventName); Q_INVOKABLE void submitEvent(const QString &eventName, const QVariant &data); - void cancelDelayedEvent(const QString &sendId); + Q_INVOKABLE void cancelDelayedEvent(const QString &sendId); - bool isDispatchableTarget(const QString &target) const; + Q_INVOKABLE bool isDispatchableTarget(const QString &target) const; + + QVector<QScxmlInvokableService *> invokedServices() const; Q_SIGNALS: void runningChanged(bool running); + void invokedServicesChanged(const QVector<QScxmlInvokableService *> &invokedServices); void log(const QString &label, const QString &msg); void reachedStableState(); void finished(); - void eventOccurred(const QScxmlEvent &event); void dataModelChanged(QScxmlDataModel *model); void initialValuesChanged(const QVariantMap &initialValues); void initializedChanged(bool initialized); - void externalEventOccurred(const QScxmlEvent &event); + void loaderChanged(QScxmlCompiler::Loader *loader); public Q_SLOTS: void start(); @@ -149,18 +331,27 @@ public Q_SLOTS: bool init(); protected: // methods for friends: - friend QScxmlDataModel; - friend QScxmlEventBuilder; - friend QScxmlInvokableServiceFactory; - friend QScxmlExecutableContent::QScxmlExecutionEngine; + friend class QScxmlDataModel; + friend class QScxmlEventBuilder; + friend class QScxmlInvokableServicePrivate; + friend class QScxmlExecutionEngine; #ifndef Q_QDOC - void setDataBinding(BindingMethod bindingMethod); - virtual void setService(const QString &id, QScxmlInvokableService *service); - + // The methods below are used by the compiled state machines. + bool isActive(int stateIndex) const; QScxmlTableData *tableData() const; void setTableData(QScxmlTableData *tableData); #endif // Q_QDOC + +private: + QMetaObject::Connection connectToStateImpl(const QString &scxmlStateName, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type = Qt::AutoConnection); + QMetaObject::Connection connectToEventImpl(const QString &scxmlEventSpec, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type = Qt::AutoConnection); }; QT_END_NAMESPACE diff --git a/src/scxml/qscxmlstatemachine_p.h b/src/scxml/qscxmlstatemachine_p.h index 99334cf..69f3be4 100644 --- a/src/scxml/qscxmlstatemachine_p.h +++ b/src/scxml/qscxmlstatemachine_p.h @@ -53,44 +53,73 @@ #include <QtScxml/private/qscxmlexecutablecontent_p.h> #include <QtScxml/qscxmlstatemachine.h> - -#include <QStateMachine> -#include <QtCore/private/qstatemachine_p.h> +#include <QtScxml/private/qscxmlstatemachineinfo_p.h> +#include <QtCore/private/qobject_p.h> +#include <QtCore/private/qmetaobject_p.h> +#include <QtCore/qmetaobject.h> QT_BEGIN_NAMESPACE namespace QScxmlInternal { -class WrappedQStateMachinePrivate; -class WrappedQStateMachine: public QStateMachine +class EventLoopHook: public QObject { Q_OBJECT - Q_DECLARE_PRIVATE(WrappedQStateMachine) -public: - WrappedQStateMachine(QScxmlStateMachine *parent); - WrappedQStateMachine(WrappedQStateMachinePrivate &dd, QScxmlStateMachine *parent); + QScxmlStateMachinePrivate *smp; - QScxmlStateMachine *stateMachine() const; +public: + EventLoopHook(QScxmlStateMachinePrivate *smp) + : smp(smp) + {} - void queueEvent(QScxmlEvent *event, QStateMachine::EventPriority priority); - void submitQueuedEvents(); - int eventIdForDelayedEvent(const QString &sendId); + void queueProcessEvents(); - Q_INVOKABLE void removeAndDestroyService(QScxmlInvokableService *service); + Q_INVOKABLE void doProcessEvents(); protected: - void beginSelectTransitions(QEvent *event) Q_DECL_OVERRIDE; - void beginMicrostep(QEvent *event) Q_DECL_OVERRIDE; - void endMicrostep(QEvent *event) Q_DECL_OVERRIDE; - bool event(QEvent *e) Q_DECL_OVERRIDE; + void timerEvent(QTimerEvent *timerEvent) Q_DECL_OVERRIDE; +}; + +class ScxmlEventRouter : public QObject +{ + Q_OBJECT +public: + ScxmlEventRouter(QObject *parent = nullptr) : QObject(parent) {} + QMetaObject::Connection connectToEvent(const QStringList &segments, const QObject *receiver, + const char *method, Qt::ConnectionType type); + QMetaObject::Connection connectToEvent(const QStringList &segments, const QObject *receiver, + void **slot, QtPrivate::QSlotObjectBase *method, + Qt::ConnectionType type); + + void route(const QStringList &segments, QScxmlEvent *event); + +signals: + void eventOccurred(const QScxmlEvent &event); private: - QScxmlStateMachinePrivate *stateMachinePrivate(); + QHash<QString, ScxmlEventRouter *> children; + ScxmlEventRouter *child(const QString &segment); + + void disconnectNotify(const QMetaMethod &signal) override; }; -} // Internal namespace + +class StateMachineInfoProxy: public QObject +{ + Q_OBJECT + +public: + StateMachineInfoProxy(QObject *parent) + : QObject(parent) + {} + +Q_SIGNALS: + void statesEntered(const QVector<QScxmlStateMachineInfo::StateId> &states); + void statesExited(const QVector<QScxmlStateMachineInfo::StateId> &states); + void transitionsTriggered(const QVector<QScxmlStateMachineInfo::TransitionId> &transitions); +}; +} // QScxmlInternal namespace class QScxmlInvokableService; -class QScxmlState; class QScxmlStateMachinePrivate: public QObjectPrivate { Q_DECLARE_PUBLIC(QScxmlStateMachine) @@ -98,6 +127,27 @@ class QScxmlStateMachinePrivate: public QObjectPrivate static QAtomicInt m_sessionIdCounter; public: // types + typedef QScxmlExecutableContent::StateTable StateTable; + + class HistoryContent + { + QHash<int, int> storage; + + public: + HistoryContent() { storage.reserve(4); } + + int &operator[](int idx) { + QHash<int, int>::Iterator i = storage.find(idx); + return (i == storage.end()) ? storage.insert(idx, StateTable::InvalidIndex).value() : + i.value(); + } + + int value(int idx) const { + QHash<int, int>::ConstIterator i = storage.constFind(idx); + return (i == storage.constEnd()) ? StateTable::InvalidIndex : i.value(); + } + }; + class ParserData { public: @@ -105,55 +155,230 @@ public: // types QVector<QScxmlError> m_errors; }; + // The OrderedSet is a set where it elements are in insertion order. See + // http://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation under Algorithm, Datatypes. It + // is used to keep lists of states and transitions in document order. + class OrderedSet + { + std::vector<int> storage; + + public: + OrderedSet(){} + OrderedSet(std::initializer_list<int> l): storage(l) {} + + std::vector<int> takeList() const + { return std::move(storage); } + + const std::vector<int> &list() const + { return storage; } + + bool contains(int i) const + { + return std::find(storage.cbegin(), storage.cend(), i) != storage.cend(); + } + + bool remove(int i) + { + std::vector<int>::iterator it = std::find(storage.begin(), storage.end(), i); + if (it == storage.end()) { + return false; + } + storage.erase(it); + return true; + } + + void removeHead() + { if (!isEmpty()) storage.erase(storage.begin()); } + + bool isEmpty() const + { return storage.empty(); } + + void add(int i) + { if (!contains(i)) storage.push_back(i); } + + bool intersectsWith(const OrderedSet &other) const + { + for (auto i : storage) { + if (other.contains(i)) { + return true; + } + } + return false; + } + + void clear() + { storage.clear(); } + + typedef std::vector<int>::const_iterator const_iterator; + const_iterator begin() const { return storage.cbegin(); } + const_iterator end() const { return storage.cend(); } + }; + + class Queue + { + QVector<QScxmlEvent *> storage; + + public: + Queue() + { storage.reserve(4); } + + ~Queue() + { qDeleteAll(storage); } + + void enqueue(QScxmlEvent *e) + { storage.append(e); } + + bool isEmpty() const + { return storage.empty(); } + + QScxmlEvent *dequeue() + { + Q_ASSERT(!isEmpty()); + QScxmlEvent *e = storage.first(); + storage.pop_front(); + int sz = storage.size(); + if (Q_UNLIKELY(sz > 4 && sz * 8 < storage.capacity())) { + storage.squeeze(); + } + return e; + } + }; + public: - QScxmlStateMachinePrivate(); + QScxmlStateMachinePrivate(const QMetaObject *qMetaObject); ~QScxmlStateMachinePrivate(); - void init(); - static QScxmlStateMachinePrivate *get(QScxmlStateMachine *t) { return t->d_func(); } - void setQStateMachine(QScxmlInternal::WrappedQStateMachine *stateMachine); - - QAbstractState *stateByScxmlName(const QString &scxmlName); + static QString generateSessionId(const QString &prefix); ParserData *parserData(); void setIsInvoked(bool invoked) { m_isInvoked = invoked; } - const QVector<QScxmlInvokableService *> &invokedServices() const - { return m_invokedServices; } - - void addService(QScxmlInvokableService *service); - - bool removeService(QScxmlInvokableService *service); + void addService(int invokingState); + void removeService(int invokingState); + QScxmlInvokableServiceFactory *serviceFactory(int id); bool executeInitialSetup(); void routeEvent(QScxmlEvent *event); void postEvent(QScxmlEvent *event); + void submitDelayedEvent(QScxmlEvent *event); void submitError(const QString &type, const QString &msg, const QString &sendid = QString()); + void start(); + void pause(); + void processEvents(); + + void setEvent(QScxmlEvent *event); + void resetEvent(); + + void emitStateActive(int stateIndex, bool active); + void emitInvokedServicesChanged(); + void emitSignalForEvent(int signalIndex, const QVariant &data); + + void attach(QScxmlStateMachineInfo *info); + const OrderedSet &configuration() const { return m_configuration; } + +private: + QStringList stateNames(const std::vector<int> &stateIndexes) const; + std::vector<int> historyStates(int stateIdx) const; + + void exitInterpreter(); + void returnDoneEvent(QScxmlExecutableContent::ContainerId doneData); + bool nameMatch(const StateTable::Array &patterns, QScxmlEvent *event) const; + void selectTransitions(OrderedSet &enabledTransitions, + const std::vector<int> &configInDocumentOrder, + QScxmlEvent *event) const; + void removeConflictingTransitions(OrderedSet *enabledTransitions) const; + void getProperAncestors(std::vector<int> *ancestors, int state1, int state2) const; + void microstep(const OrderedSet &enabledTransitions); + void exitStates(const OrderedSet &enabledTransitions); + void computeExitSet(const OrderedSet &enabledTransitions, OrderedSet &statesToExit) const; + void executeTransitionContent(const OrderedSet &enabledTransitions); + void enterStates(const OrderedSet &enabledTransitions); + void computeEntrySet(const OrderedSet &enabledTransitions, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const; + void addDescendantStatesToEnter(int stateIndex, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const; + void addAncestorStatesToEnter(int stateIndex, + int ancestorIndex, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const; + std::vector<int> getChildStates(const StateTable::State &state) const; + bool hasDescendant(const OrderedSet &statesToEnter, int childIdx) const; + bool allDescendants(const OrderedSet &statesToEnter, int childdx) const; + bool isDescendant(int state1, int state2) const; + bool allInFinalStates(const std::vector<int> &states) const; + bool someInFinalStates(const std::vector<int> &states) const; + bool isInFinalState(int stateIndex) const; + int getTransitionDomain(int transitionIndex) const; + int findLCCA(OrderedSet &&states) const; + void getEffectiveTargetStates(OrderedSet *targets, int transitionIndex) const; + public: // types & data fields: QString m_sessionId; bool m_isInvoked; bool m_isInitialized; + bool m_isProcessingEvents; QVariantMap m_initialValues; QScxmlDataModel *m_dataModel; - QScxmlStateMachine::BindingMethod m_dataBinding; - QScxmlExecutableContent::QScxmlExecutionEngine *m_executionEngine; + QScxmlCompilerPrivate::DefaultLoader m_defaultLoader; + QScxmlCompiler::Loader *m_loader; + QScxmlExecutionEngine *m_executionEngine; QScxmlTableData *m_tableData; - QScxmlEvent m_event; - QScxmlInternal::WrappedQStateMachine *m_qStateMachine; - QScxmlEventFilter *m_eventFilter; - QVector<QScxmlState*> m_statesToInvoke; + const StateTable *m_stateTable; QScxmlStateMachine *m_parentStateMachine; + QScxmlInternal::EventLoopHook m_eventLoopHook; + typedef std::vector<std::pair<int, QScxmlEvent *>> DelayedQueue; + DelayedQueue m_delayedEvents; + const QMetaObject *m_metaObject; + QScxmlInternal::ScxmlEventRouter m_router; private: - QVector<QScxmlInvokableService *> m_invokedServices; QScopedPointer<ParserData> m_parserData; // used when created by StateMachine::fromFile. + typedef QHash<int, QVector<int>> HistoryValues; + struct InvokedService { + int invokingState; + QScxmlInvokableService *service; + QString serviceName; + }; + + // TODO: move the stuff below to a struct that can be reset + HistoryValues m_historyValue; + OrderedSet m_configuration; + Queue m_internalQueue; + Queue m_externalQueue; + QSet<int> m_statesToInvoke; + std::vector<InvokedService> m_invokedServices; + std::vector<bool> m_isFirstStateEntry; + std::vector<QScxmlInvokableServiceFactory *> m_cachedFactories; + enum { Invalid = 0, Starting, Running, Paused, Finished } m_runningState = Invalid; + bool isRunnable() const { + switch (m_runningState) { + case Starting: + case Running: + case Paused: + return true; + case Invalid: + case Finished: + return false; + } + + return false; // Dead code, but many dumb compilers cannot (or are unwilling to) detect that. + } + + bool isPaused() const { return m_runningState == Paused; } + + QScxmlInternal::StateMachineInfoProxy *m_infoSignalProxy; }; QT_END_NAMESPACE diff --git a/src/scxml/qscxmlstatemachineinfo.cpp b/src/scxml/qscxmlstatemachineinfo.cpp new file mode 100644 index 0000000..d81956a --- /dev/null +++ b/src/scxml/qscxmlstatemachineinfo.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscxmlstatemachineinfo_p.h" +#include "qscxmlstatemachine_p.h" +#include "qscxmlexecutablecontent_p.h" + +QT_BEGIN_NAMESPACE + +class QScxmlStateMachineInfoPrivate: public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QScxmlStateMachineInfo) + +public: + QScxmlStateMachine *stateMachine() const + { return qobject_cast<QScxmlStateMachine *>(q_func()->parent()); } + + QScxmlStateMachinePrivate *stateMachinePrivate() const + { return QScxmlStateMachinePrivate::get(stateMachine()); } + + const QScxmlExecutableContent::StateTable *stateTable() const + { return stateMachinePrivate()->m_stateTable; } +}; + +QScxmlStateMachineInfo::QScxmlStateMachineInfo(QScxmlStateMachine *stateMachine) + : QObject(*new QScxmlStateMachineInfoPrivate, stateMachine) +{ + QScxmlStateMachinePrivate::get(stateMachine)->attach(this); +} + +QScxmlStateMachine *QScxmlStateMachineInfo::stateMachine() const +{ + Q_D(const QScxmlStateMachineInfo); + + return d->stateMachine(); +} + +QVector<QScxmlStateMachineInfo::StateId> QScxmlStateMachineInfo::allStates() const +{ + Q_D(const QScxmlStateMachineInfo); + + QVector<QScxmlStateMachineInfo::StateId> all; + for (int i = 0, ei = d->stateTable()->stateCount; i < ei; ++i) { + all.append(i); + } + return all; +} + +QVector<QScxmlStateMachineInfo::TransitionId> QScxmlStateMachineInfo::allTransitions() const +{ + Q_D(const QScxmlStateMachineInfo); + + QVector<QScxmlStateMachineInfo::TransitionId> all; + for (int i = 0, ei = d->stateTable()->transitionCount; i < ei; ++i) { + all.append(i); + } + return all; +} + +QString QScxmlStateMachineInfo::stateName(int stateId) const +{ + Q_D(const QScxmlStateMachineInfo); + + if (stateId < 0 || stateId >= d->stateTable()->stateCount) + return QString(); + + auto state = d->stateTable()->state(stateId); + if (state.name >= 0) + return d->stateMachinePrivate()->m_tableData->string(state.name); + else + return QString(); +} + +QScxmlStateMachineInfo::StateId QScxmlStateMachineInfo::stateParent(StateId stateId) const +{ + Q_D(const QScxmlStateMachineInfo); + + if (stateId < 0 || stateId >= d->stateTable()->stateCount) + return InvalidStateId; + + auto state = d->stateTable()->state(stateId); + return state.parent; +} + +QScxmlStateMachineInfo::StateType QScxmlStateMachineInfo::stateType(StateId stateId) const +{ + Q_D(const QScxmlStateMachineInfo); + + if (stateId < 0 || stateId >= d->stateTable()->stateCount) + return InvalidState; + + auto state = d->stateTable()->state(stateId); + switch (state.type) { + default: return InvalidState; + case QScxmlExecutableContent::StateTable::State::Normal: return NormalState; + case QScxmlExecutableContent::StateTable::State::Parallel: return ParallelState; + case QScxmlExecutableContent::StateTable::State::Final: return FinalState; + case QScxmlExecutableContent::StateTable::State::ShallowHistory: return ShallowHistoryState; + case QScxmlExecutableContent::StateTable::State::DeepHistory: return DeepHistoryState; + } +} + +QVector<QScxmlStateMachineInfo::StateId> QScxmlStateMachineInfo::stateChildren(StateId stateId) const +{ + Q_D(const QScxmlStateMachineInfo); + + int childStates = QScxmlExecutableContent::StateTable::InvalidIndex; + if (stateId == InvalidStateId) + childStates = d->stateTable()->childStates; + if (stateId >= 0 && stateId < d->stateTable()->stateCount) + childStates = d->stateTable()->state(stateId).childStates; + + QVector<QScxmlStateMachineInfo::StateId> all; + if (childStates == QScxmlExecutableContent::StateTable::InvalidIndex) + return all; + + const auto kids = d->stateTable()->array(childStates); + all.reserve(kids.size()); + for (auto childId : kids) { + all.append(childId); + } + return all; +} + +QScxmlStateMachineInfo::TransitionType QScxmlStateMachineInfo::transitionType(QScxmlStateMachineInfo::TransitionId transitionId) const +{ + Q_D(const QScxmlStateMachineInfo); + + if (transitionId < 0 || transitionId >= d->stateTable()->transitionCount) + return InvalidTransition; + + auto transition = d->stateTable()->transition(transitionId); + switch (transition.type) { + default: return InvalidTransition; + case QScxmlExecutableContent::StateTable::Transition::Invalid: return InvalidTransition; + case QScxmlExecutableContent::StateTable::Transition::Internal: return InternalTransition; + case QScxmlExecutableContent::StateTable::Transition::External: return ExternalTransition; + case QScxmlExecutableContent::StateTable::Transition::Synthetic: return SyntheticTransition; + } +} + +QScxmlStateMachineInfo::TransitionId QScxmlStateMachineInfo::initialTransition(StateId stateId) const +{ + Q_D(const QScxmlStateMachineInfo); + + if (stateId == InvalidStateId) + return d->stateTable()->initialTransition; + + if (stateId < 0 || stateId >= d->stateTable()->stateCount) + return InvalidTransitionId; + + return d->stateTable()->state(stateId).initialTransition; +} + +QScxmlStateMachineInfo::StateId QScxmlStateMachineInfo::transitionSource(TransitionId transitionId) const +{ + Q_D(const QScxmlStateMachineInfo); + + if (transitionId < 0 || transitionId >= d->stateTable()->transitionCount) + return InvalidStateId; + + auto transition = d->stateTable()->transition(transitionId); + return transition.source; +} + +QVector<QScxmlStateMachineInfo::StateId> QScxmlStateMachineInfo::transitionTargets(TransitionId transitionId) const +{ + Q_D(const QScxmlStateMachineInfo); + + QVector<QScxmlStateMachineInfo::StateId> targets; + if (transitionId < 0 || transitionId >= d->stateTable()->transitionCount) + return targets; + + auto transition = d->stateTable()->transition(transitionId); + if (transition.targets == QScxmlExecutableContent::StateTable::InvalidIndex) + return targets; + + for (int target : d->stateTable()->array(transition.targets)) { + targets.append(target); + } + + return targets; +} + +QVector<QString> QScxmlStateMachineInfo::transitionEvents(TransitionId transitionId) const +{ + Q_D(const QScxmlStateMachineInfo); + + QVector<QString> events; + if (transitionId < 0 || transitionId >= d->stateTable()->transitionCount) + return events; + + auto transition = d->stateTable()->transition(transitionId); + if (transition.events == QScxmlExecutableContent::StateTable::InvalidIndex) + return events; + + auto eventIds = d->stateTable()->array(transition.events); + events.reserve(eventIds.size()); + for (auto eventId : eventIds) { + events.append(d->stateMachinePrivate()->m_tableData->string(eventId)); + } + + return events; +} + +QVector<QScxmlStateMachineInfo::StateId> QScxmlStateMachineInfo::configuration() const +{ + Q_D(const QScxmlStateMachineInfo); + + return QVector<StateId>::fromStdVector(d->stateMachinePrivate()->configuration().list()); +} + +QT_END_NAMESPACE diff --git a/src/scxml/qscxmlstatemachineinfo_p.h b/src/scxml/qscxmlstatemachineinfo_p.h new file mode 100644 index 0000000..7a8ca50 --- /dev/null +++ b/src/scxml/qscxmlstatemachineinfo_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCXMLSTATEMACHINEINFO_H +#define QSCXMLSTATEMACHINEINFO_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtScxml/qscxmlglobals.h> +#include <QObject> + +QT_BEGIN_NAMESPACE + +class QScxmlStateMachine; +class QScxmlStateMachineInfoPrivate; + +class Q_SCXML_EXPORT QScxmlStateMachineInfo: public QObject +{ + Q_OBJECT + +public: // types + typedef int StateId; + typedef int TransitionId; + + static const StateId InvalidStateId = -1; + static const TransitionId InvalidTransitionId = -1; + + enum StateType : int { + InvalidState = -1, + NormalState = 0, + ParallelState = 1, + FinalState = 2, + ShallowHistoryState = 3, + DeepHistoryState = 4 + }; + + enum TransitionType : int { + InvalidTransition = -1, + InternalTransition = 0, + ExternalTransition = 1, + SyntheticTransition = 2 + }; + +public: // methods + QScxmlStateMachineInfo(QScxmlStateMachine *stateMachine); + + QScxmlStateMachine *stateMachine() const; + + QVector<StateId> allStates() const; + QVector<TransitionId> allTransitions() const; + QString stateName(int stateId) const; + StateId stateParent(StateId stateId) const; + StateType stateType(int stateId) const; + QVector<StateId> stateChildren(StateId stateId) const; + TransitionId initialTransition(StateId stateId) const; + TransitionType transitionType(TransitionId transitionId) const; + StateId transitionSource(TransitionId transitionId) const; + QVector<StateId> transitionTargets(TransitionId transitionId) const; + QVector<QString> transitionEvents(TransitionId transitionId) const; + QVector<StateId> configuration() const; + +Q_SIGNALS: + void statesEntered(const QVector<QScxmlStateMachineInfo::StateId> &states); + void statesExited(const QVector<QScxmlStateMachineInfo::StateId> &states); + void transitionsTriggered(const QVector<QScxmlStateMachineInfo::TransitionId> &transitions); + +private: + Q_DECLARE_PRIVATE(QScxmlStateMachineInfo) +}; + +QT_END_NAMESPACE + +#endif // QSCXMLSTATEMACHINEINFO_H diff --git a/src/scxml/qscxmltabledata.cpp b/src/scxml/qscxmltabledata.cpp index 75469f9..2457840 100644 --- a/src/scxml/qscxmltabledata.cpp +++ b/src/scxml/qscxmltabledata.cpp @@ -37,11 +37,1005 @@ ** ****************************************************************************/ -#include "qscxmltabledata.h" +#include "qscxmltabledata_p.h" +#include "qscxmlcompiler_p.h" +#include "qscxmlexecutablecontent_p.h" -QT_BEGIN_NAMESPACE +QT_USE_NAMESPACE + +using namespace QScxmlInternal; + +namespace { +using namespace QScxmlExecutableContent; + +class TableDataBuilder: public DocumentModel::NodeVisitor +{ +public: + TableDataBuilder(GeneratedTableData &tableData, + GeneratedTableData::MetaDataInfo &metaDataInfo, + GeneratedTableData::DataModelInfo &dataModelInfo, + GeneratedTableData::CreateFactoryId func) + : createFactoryId(func) + , m_tableData(tableData) + , m_dataModelInfo(dataModelInfo) + , m_stringTable(tableData.theStrings) + , m_instructions(tableData.theInstructions) + , m_evaluators(tableData.theEvaluators) + , m_assignments(tableData.theAssignments) + , m_foreaches(tableData.theForeaches) + , m_dataIds(tableData.theDataNameIds) + , m_stateNames(metaDataInfo.stateNames) + + { + m_activeSequences.reserve(4); + tableData.theInitialSetup = QScxmlExecutableContent::NoInstruction; + } + + void buildTableData(DocumentModel::ScxmlDocument *doc) + { + m_isCppDataModel = doc->root->dataModel == DocumentModel::Scxml::CppDataModel; + m_parents.reserve(32); + m_allTransitions.resize(doc->allTransitions.size()); + m_docTransitionIndices.reserve(doc->allTransitions.size()); + for (auto *t : qAsConst(doc->allTransitions)) { + m_docTransitionIndices.insert(t, m_docTransitionIndices.size()); + } + m_docStatesIndices.reserve(doc->allStates.size()); + m_transitionsForState.resize(doc->allStates.size()); + m_allStates.resize(doc->allStates.size()); + for (DocumentModel::AbstractState *s : qAsConst(doc->allStates)) { + m_docStatesIndices.insert(s, m_docStatesIndices.size()); + } + + doc->root->accept(this); + m_stateTable.version = Q_QSCXMLC_OUTPUT_REVISION; + generateStateMachineData(); + + m_tableData.theInstructions.squeeze(); + } + + void generateStateMachineData() + { + const int tableSize = sizeof(StateTable) / sizeof(qint32); + const int stateSize = qint32(sizeof(StateTable::State) / sizeof(qint32)); + const int transitionSize = qint32(sizeof(StateTable::Transition) / sizeof(qint32)); + + m_stateTable.stateOffset = tableSize; + m_stateTable.stateCount = m_allStates.size(); + m_stateTable.transitionOffset = m_stateTable.stateOffset + + m_stateTable.stateCount * stateSize; + m_stateTable.transitionCount = m_allTransitions.size(); + m_stateTable.arrayOffset = m_stateTable.transitionOffset + + m_stateTable.transitionCount * transitionSize; + m_stateTable.arraySize = m_arrays.size(); + + const qint32 dataSize = qint32(tableSize) + + (m_allStates.size() * stateSize) + + (m_allTransitions.size() * transitionSize) + + m_arrays.size() + + 1; + QVector<qint32> data(dataSize, -1); + qint32 *ptr = data.data(); + + memcpy(ptr, &m_stateTable, sizeof(m_stateTable)); + ptr += tableSize; + + Q_ASSERT(ptr == data.constData() + m_stateTable.stateOffset); + memcpy(ptr, m_allStates.constData(), + sizeof(StateTable::State) * size_t(m_allStates.size())); + ptr += stateSize * size_t(m_allStates.size()); + + Q_ASSERT(ptr == data.constData() + m_stateTable.transitionOffset); + memcpy(ptr, m_allTransitions.constData(), + sizeof(StateTable::Transition) * size_t(m_allTransitions.size())); + ptr += transitionSize * size_t(m_allTransitions.size()); + + Q_ASSERT(ptr == data.constData() + m_stateTable.arrayOffset); + memcpy(ptr, m_arrays.constData(), sizeof(qint32) * size_t(m_arrays.size())); + ptr += m_arrays.size(); + + *ptr++ = StateTable::terminator; + + Q_ASSERT(ptr == data.constData() + dataSize); + + m_tableData.theStateMachineTable = data; + } + +protected: // visitor + using NodeVisitor::visit; + + bool visit(DocumentModel::Scxml *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + setName(node->name); + + switch (node->dataModel) { + case DocumentModel::Scxml::NullDataModel: + m_stateTable.dataModel = StateTable::NullDataModel; + break; + case DocumentModel::Scxml::JSDataModel: + m_stateTable.dataModel = StateTable::EcmaScriptDataModel; + break; + case DocumentModel::Scxml::CppDataModel: + m_stateTable.dataModel = StateTable::CppDataModel; + break; + default: + m_stateTable.dataModel = StateTable::InvalidDataModel; + break; + } + + switch (node->binding) { + case DocumentModel::Scxml::EarlyBinding: + m_stateTable.binding = StateTable::EarlyBinding; + break; + case DocumentModel::Scxml::LateBinding: + m_stateTable.binding = StateTable::LateBinding; + m_bindLate = true; + break; + default: + Q_UNREACHABLE(); + } + + m_stateTable.name = addString(node->name); + + m_parents.append(-1); + visit(node->children); + + m_dataElements.append(node->dataElements); + if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { + setInitialSetup(startNewSequence()); + generate(m_dataElements); + if (node->script) { + node->script->accept(this); + } + visit(&node->initialSetup); + endSequence(); + } + + QVector<DocumentModel::AbstractState *> childStates; + for (DocumentModel::StateOrTransition *sot : qAsConst(node->children)) { + if (DocumentModel::AbstractState *s = sot->asAbstractState()) { + childStates.append(s); + } + } + m_stateTable.childStates = addStates(childStates); + if (node->initialTransition) { + visit(node->initialTransition); + const int transitionIndex = m_docTransitionIndices.value(node->initialTransition, -1); + Q_ASSERT(transitionIndex != -1); + m_stateTable.initialTransition = transitionIndex; + } + m_parents.removeLast(); + + return false; + } + + bool visit(DocumentModel::State *state) Q_DECL_OVERRIDE Q_DECL_FINAL + { + m_stateNames.add(state->id); + const int stateIndex = m_docStatesIndices.value(state, -1); + Q_ASSERT(stateIndex != -1); + StateTable::State &newState = m_allStates[stateIndex]; + newState.name = addString(state->id); + newState.parent = currentParent(); + + switch (state->type) { + case DocumentModel::State::Normal: + newState.type = StateTable::State::Normal; + break; + case DocumentModel::State::Parallel: + newState.type = StateTable::State::Parallel; + break; + case DocumentModel::State::Final: + newState.type = StateTable::State::Final; + newState.doneData = generate(state->doneData); + break; + default: + Q_UNREACHABLE(); + } + + m_parents.append(stateIndex); + + if (!state->dataElements.isEmpty()) { + if (m_bindLate) { + newState.initInstructions = startNewSequence(); + generate(state->dataElements); + endSequence(); + } else { + m_dataElements.append(state->dataElements); + } + } + + newState.entryInstructions = generate(state->onEntry); + newState.exitInstructions = generate(state->onExit); + if (!state->invokes.isEmpty()) { + QVector<int> factoryIds; + for (DocumentModel::Invoke *invoke : qAsConst(state->invokes)) { + auto ctxt = createContext(QStringLiteral("invoke")); + QVector<QScxmlExecutableContent::StringId> namelist; + for (const QString &name : qAsConst(invoke->namelist)) + namelist += addString(name); + QVector<QScxmlExecutableContent::ParameterInfo> params; + for (DocumentModel::Param *param : qAsConst(invoke->params)) { + QScxmlExecutableContent::ParameterInfo p; + p.name = addString(param->name); + p.expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), + param->expr); + p.location = addString(param->location); + params.append(p); + } + QScxmlExecutableContent::ContainerId finalize = + QScxmlExecutableContent::NoInstruction; + if (!invoke->finalize.isEmpty()) { + finalize = startNewSequence(); + visit(&invoke->finalize); + endSequence(); + } + auto srcexpr = createEvaluatorString(QStringLiteral("invoke"), + QStringLiteral("srcexpr"), + invoke->srcexpr); + QScxmlExecutableContent::InvokeInfo invokeInfo; + invokeInfo.id = addString(invoke->id); + invokeInfo.prefix = addString(state->id + QStringLiteral(".session-")); + invokeInfo.location = addString(invoke->idLocation); + invokeInfo.context = ctxt; + invokeInfo.expr = srcexpr; + invokeInfo.finalize = finalize; + invokeInfo.autoforward = invoke->autoforward; + const int factoryId = createFactoryId(invokeInfo, namelist, params, + invoke->content); + Q_ASSERT(factoryId >= 0); + factoryIds.append(factoryId); + m_stateTable.maxServiceId = std::max(m_stateTable.maxServiceId, factoryId); + } + newState.serviceFactoryIds = addArray(factoryIds); + } + + visit(state->children); + + QVector<DocumentModel::AbstractState *> childStates; + for (DocumentModel::StateOrTransition *sot : qAsConst(state->children)) { + if (auto s = sot->asAbstractState()) { + childStates.append(s); + } + } + newState.childStates = addStates(childStates); + newState.transitions = addArray(m_transitionsForState.at(stateIndex)); + if (state->initialTransition) { + visit(state->initialTransition); + newState.initialTransition = m_transitionsForState.at(stateIndex).last(); + } + m_parents.removeLast(); + + return false; + } + + bool visit(DocumentModel::Transition *transition) Q_DECL_OVERRIDE Q_DECL_FINAL + { + const int transitionIndex = m_docTransitionIndices.value(transition, -1); + Q_ASSERT(transitionIndex != -1); + StateTable::Transition &newTransition = m_allTransitions[transitionIndex]; + const int parentIndex = currentParent(); + if (parentIndex != -1) { + m_transitionsForState[parentIndex].append(transitionIndex); + } + newTransition.source = parentIndex; + + if (transition->condition) { + newTransition.condition = createEvaluatorBool(QStringLiteral("transition"), + QStringLiteral("cond"), + *transition->condition.data()); + } + + switch (transition->type) { + case DocumentModel::Transition::External: + newTransition.type = StateTable::Transition::External; + break; + case DocumentModel::Transition::Internal: + newTransition.type = StateTable::Transition::Internal; + break; + case DocumentModel::Transition::Synthetic: + newTransition.type = StateTable::Transition::Synthetic; + break; + default: + Q_UNREACHABLE(); + } + + if (!transition->instructionsOnTransition.isEmpty()) { + m_currentTransition = transitionIndex; + newTransition.transitionInstructions = startNewSequence(); + visit(&transition->instructionsOnTransition); + endSequence(); + m_currentTransition = -1; + } + + newTransition.targets = addStates(transition->targetStates); + + QVector<int> eventIds; + for (const QString &event : qAsConst(transition->events)) + eventIds.push_back(addString(event)); + + newTransition.events = addArray(eventIds); + + return false; + } + + bool visit(DocumentModel::HistoryState *historyState) Q_DECL_OVERRIDE Q_DECL_FINAL + { + const int stateIndex = m_docStatesIndices.value(historyState, -1); + Q_ASSERT(stateIndex != -1); + StateTable::State &newState = m_allStates[stateIndex]; + newState.name = addString(historyState->id); + newState.parent = currentParent(); + + switch (historyState->type) { + case DocumentModel::HistoryState::Shallow: + newState.type = StateTable::State::ShallowHistory; + break; + case DocumentModel::HistoryState::Deep: + newState.type = StateTable::State::DeepHistory; + break; + default: + Q_UNREACHABLE(); + } + + m_parents.append(stateIndex); + visit(historyState->children); + m_parents.removeLast(); + newState.transitions = addArray(m_transitionsForState.at(stateIndex)); + return false; + } + + bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Send>(Send::calculateExtraSize(node->params.size(), + node->namelist.size())); + instr->instructionLocation = createContext(QStringLiteral("send")); + instr->event = addString(node->event); + instr->eventexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("eventexpr"), + node->eventexpr); + instr->type = addString(node->type); + instr->typeexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("typeexpr"), + node->typeexpr); + instr->target = addString(node->target); + instr->targetexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("targetexpr"), + node->targetexpr); + instr->id = addString(node->id); + instr->idLocation = addString(node->idLocation); + instr->delay = addString(node->delay); + instr->delayexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("delayexpr"), + node->delayexpr); + instr->content = addString(node->content); + instr->contentexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("contentexpr"), + node->contentexpr); + generate(&instr->namelist, node->namelist); + generate(instr->params(), node->params); + return false; + } + + void visit(DocumentModel::Raise *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Raise>(); + instr->event = addString(node->event); + } + + void visit(DocumentModel::Log *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Log>(); + instr->label = addString(node->label); + instr->expr = createEvaluatorString(QStringLiteral("log"), + QStringLiteral("expr"), + node->expr); + } + + void visit(DocumentModel::Script *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<JavaScript>(); + instr->go = createEvaluatorVoid(QStringLiteral("script"), + QStringLiteral("source"), + node->content); + } + + void visit(DocumentModel::Assign *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Assign>(); + auto ctxt = createContext(QStringLiteral("assign"), QStringLiteral("expr"), node->expr); + instr->expression = addAssignment(node->location, node->expr, ctxt); + } + + bool visit(DocumentModel::If *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<If>(node->conditions.size()); + instr->conditions.count = node->conditions.size(); + auto it = instr->conditions.data(); + QString tag = QStringLiteral("if"); + for (int i = 0, ei = node->conditions.size(); i != ei; ++i) { + *it++ = createEvaluatorBool(tag, QStringLiteral("cond"), node->conditions.at(i)); + if (i == 0) { + tag = QStringLiteral("elif"); + } + } + auto outSequences = m_instructions.add<InstructionSequences>(); + generate(outSequences, node->blocks); + return false; + } + + bool visit(DocumentModel::Foreach *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Foreach>(); + auto ctxt = createContextString(QStringLiteral("foreach")); + instr->doIt = addForeach(node->array, node->item, node->index, ctxt); + startSequence(&instr->block); + visit(&node->block); + endSequence(); + return false; + } + + void visit(DocumentModel::Cancel *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Cancel>(); + instr->sendid = addString(node->sendid); + instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel"), + QStringLiteral("sendidexpr"), + node->sendidexpr); + } + +protected: + static int paramSize() { return sizeof(ParameterInfo) / sizeof(qint32); } + + ContainerId generate(const DocumentModel::DoneData *node) + { + auto id = m_instructions.newContainerId(); + DoneData *doneData; + if (node) { + doneData = m_instructions.add<DoneData>(node->params.size() * paramSize()); + doneData->contents = addString(node->contents); + doneData->expr = createEvaluatorString(QStringLiteral("donedata"), + QStringLiteral("expr"), + node->expr); + generate(&doneData->params, node->params); + } else { + doneData = m_instructions.add<DoneData>(); + doneData->contents = NoString; + doneData->expr = NoEvaluator; + doneData->params.count = 0; + } + doneData->location = createContext(QStringLiteral("final")); + return id; + } + + StringId createContext(const QString &instrName) + { + return addString(createContextString(instrName)); + } + + void generate(const QVector<DocumentModel::DataElement *> &dataElements) + { + for (DocumentModel::DataElement *el : dataElements) { + auto ctxt = createContext(QStringLiteral("data"), QStringLiteral("expr"), el->expr); + auto evaluator = addDataElement(el->id, el->expr, ctxt); + if (evaluator != NoEvaluator) { + auto instr = m_instructions.add<QScxmlExecutableContent::Initialize>(); + instr->expression = evaluator; + } + } + } + + ContainerId generate(const DocumentModel::InstructionSequences &inSequences) + { + if (inSequences.isEmpty()) + return NoInstruction; + + auto id = m_instructions.newContainerId(); + auto outSequences = m_instructions.add<InstructionSequences>(); + generate(outSequences, inSequences); + return id; + } + + void generate(Array<ParameterInfo> *out, const QVector<DocumentModel::Param *> &in) + { + out->count = in.size(); + ParameterInfo *it = out->data(); + for (DocumentModel::Param *f : in) { + it->name = addString(f->name); + it->expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), + f->expr); + it->location = addString(f->location); + ++it; + } + } + + void generate(InstructionSequences *outSequences, + const DocumentModel::InstructionSequences &inSequences) + { + int sequencesOffset = m_instructions.offset(outSequences); + int sequenceCount = 0; + int entryCount = 0; + for (DocumentModel::InstructionSequence *sequence : inSequences) { + ++sequenceCount; + startNewSequence(); + visit(sequence); + entryCount += endSequence()->size(); + } + outSequences = m_instructions.at<InstructionSequences>(sequencesOffset); + outSequences->sequenceCount = sequenceCount; + outSequences->entryCount = entryCount; + } + + void generate(Array<StringId> *out, const QStringList &in) + { + out->count = in.size(); + StringId *it = out->data(); + for (const QString &str : in) { + *it++ = addString(str); + } + } + + ContainerId startNewSequence() + { + auto id = m_instructions.newContainerId(); + auto sequence = m_instructions.add<InstructionSequence>(); + startSequence(sequence); + return id; + } + + void startSequence(InstructionSequence *sequence) + { + SequenceInfo info; + info.location = m_instructions.offset(sequence); + info.entryCount = 0; + m_activeSequences.push_back(info); + m_instructions.setSequenceInfo(&m_activeSequences.last()); + sequence->instructionType = Instruction::Sequence; + sequence->entryCount = -1; // checked in endSequence + } + + InstructionSequence *endSequence() + { + SequenceInfo info = m_activeSequences.back(); + m_activeSequences.pop_back(); + m_instructions.setSequenceInfo(m_activeSequences.isEmpty() ? Q_NULLPTR : + &m_activeSequences.last()); + + auto sequence = m_instructions.at<InstructionSequence>(info.location); + Q_ASSERT(sequence->entryCount == -1); // set in startSequence + sequence->entryCount = info.entryCount; + if (!m_activeSequences.isEmpty()) + m_activeSequences.last().entryCount += info.entryCount; + return sequence; + } + + EvaluatorId createEvaluatorString(const QString &instrName, const QString &attrName, + const QString &expr) + { + if (!expr.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.stringEvaluators.insert(id, expr); + return id; + } else { + return addEvaluator(expr, createContext(instrName, attrName, expr)); + } + } + + return NoEvaluator; + } + + EvaluatorId createEvaluatorBool(const QString &instrName, const QString &attrName, + const QString &cond) + { + if (!cond.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.boolEvaluators.insert(id, cond); + return id; + } else { + return addEvaluator(cond, createContext(instrName, attrName, cond)); + } + } + + return NoEvaluator; + } + + EvaluatorId createEvaluatorVariant(const QString &instrName, const QString &attrName, + const QString &expr) + { + if (!expr.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.variantEvaluators.insert(id, expr); + return id; + } else { + return addEvaluator(expr, createContext(instrName, attrName, expr)); + } + } + + return NoEvaluator; + } + + EvaluatorId createEvaluatorVoid(const QString &instrName, const QString &attrName, + const QString &stuff) + { + if (!stuff.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.voidEvaluators.insert(id, stuff); + return id; + } else { + return addEvaluator(stuff, createContext(instrName, attrName, stuff)); + } + } + + return NoEvaluator; + } + + GeneratedTableData *tableData(const QVector<int> &stateMachineTable); + + StringId addString(const QString &str) + { return str.isEmpty() ? NoString : m_stringTable.add(str); } + + void setInitialSetup(ContainerId id) + { m_tableData.theInitialSetup = id; } + + void setName(const QString &name) + { m_tableData.theName = addString(name); } + + bool isCppDataModel() const + { return m_isCppDataModel; } + + int addStates(const QVector<DocumentModel::AbstractState *> &states) + { + QVector<int> array; + for (auto *s : states) { + int si = m_docStatesIndices.value(s, -1); + Q_ASSERT(si != -1); + array.push_back(si); + } + + return addArray(array); + } + + int addArray(const QVector<int> &array) + { + if (array.isEmpty()) + return -1; + + const int res = m_arrays.size(); + m_arrays.push_back(array.size()); + m_arrays.append(array); + return res; + } + + int currentParent() const + { + return m_parents.last(); + } + + QString createContextString(const QString &instrName) const + { + if (m_currentTransition != -1) { + QString state; + int parent = m_allTransitions.at(m_currentTransition).source; + if (parent != -1) { + QString parentName = QStringLiteral("(none)"); + int name = m_allStates.at(parent).name; + if (name != -1) { + parentName = m_stringTable.item(name); + } + state = QStringLiteral(" of state '%1'").arg(parentName); + } + return QStringLiteral("%1 instruction in transition %3").arg(instrName, state); + } else { + QString parentName = QStringLiteral("(none)"); + const int parent = currentParent(); + if (parent != -1) { + const int name = m_allStates.at(parent).name; + if (name != -1) { + parentName = m_stringTable.item(name); + } + } + return QStringLiteral("%1 instruction in state %2").arg(instrName, parentName); + } + } + + QString createContext(const QString &instrName, const QString &attrName, + const QString &attrValue) const + { + const QString location = createContextString(instrName); + return QStringLiteral("%1 with %2=\"%3\"").arg(location, attrName, attrValue); + } + + EvaluatorId addEvaluator(const QString &expr, const QString &context) + { + EvaluatorInfo ei; + ei.expr = addString(expr); + ei.context = addString(context); + return m_evaluators.add(ei); + } + + EvaluatorId addAssignment(const QString &dest, const QString &expr, const QString &context) + { + AssignmentInfo ai; + ai.dest = addString(dest); + ai.expr = addString(expr); + ai.context = addString(context); + return m_assignments.add(ai); + } + + EvaluatorId addForeach(const QString &array, const QString &item, const QString &index, + const QString &context) + { + ForeachInfo fi; + fi.array = addString(array); + fi.item = addString(item); + fi.index = addString(index); + fi.context = addString(context); + return m_foreaches.add(fi); + } + + EvaluatorId addDataElement(const QString &id, const QString &expr, const QString &context) + { + auto str = addString(id); + if (!m_dataIds.contains(str)) + m_dataIds.append(str); + if (expr.isEmpty()) + return NoEvaluator; + + return addAssignment(id, expr, context); + } + +private: + template <class Container, typename T, typename U> + class Table { + Container &elements; + QMap<T, int> indexForElement; + + public: + Table(Container &storage) + : elements(storage) + {} + + U add(const T &s, bool uniqueOnly = true) { + int pos = uniqueOnly ? indexForElement.value(s, -1) : -1; + if (pos == -1) { + pos = elements.size(); + elements.append(s); + indexForElement.insert(s, pos); + } + return pos; + } + + Container data() { + return elements; + } + + const T &item(U pos) const { + return elements.at(pos); + } + }; + + struct SequenceInfo { + int location; + qint32 entryCount; // the amount of qint32's that the instructions take up + }; + + class InstructionStorage { + public: + InstructionStorage(QVector<qint32> &storage) + : m_instr(storage) + , m_info(Q_NULLPTR) + {} + + ContainerId newContainerId() const { return m_instr.size(); } + + template <typename T> + T *add(int extra = 0) + { + const int pos = m_instr.size(); + const int size = sizeof(T) / sizeof(qint32) + extra; + if (m_info) + m_info->entryCount += size; + m_instr.resize(pos + size); + T *instr = at<T>(pos); + Q_ASSERT(instr->instructionType == 0); + instr->instructionType = T::kind(); + return instr; + } + + int offset(Instruction *instr) const + { + return reinterpret_cast<qint32 *>(instr) - m_instr.data(); + } + + template <typename T> + T *at(int offset) + { + return reinterpret_cast<T *>(&m_instr[offset]); + } + + void setSequenceInfo(SequenceInfo *info) + { + m_info = info; + } + + private: + QVector<qint32> &m_instr; + SequenceInfo *m_info; + }; + + QVector<SequenceInfo> m_activeSequences; + + GeneratedTableData::CreateFactoryId createFactoryId; + GeneratedTableData &m_tableData; + GeneratedTableData::DataModelInfo &m_dataModelInfo; + Table<QStringList, QString, StringId> m_stringTable; + InstructionStorage m_instructions; + Table<QVector<EvaluatorInfo>, EvaluatorInfo, EvaluatorId> m_evaluators; + Table<QVector<AssignmentInfo>, AssignmentInfo, EvaluatorId> m_assignments; + Table<QVector<ForeachInfo>, ForeachInfo, EvaluatorId> m_foreaches; + QVector<StringId> &m_dataIds; + bool m_isCppDataModel = false; + + StateTable m_stateTable; + QVector<int> m_parents; + QVector<qint32> m_arrays; + + QVector<StateTable::Transition> m_allTransitions; + QHash<DocumentModel::Transition *, int> m_docTransitionIndices; + QVector<StateTable::State> m_allStates; + QHash<DocumentModel::AbstractState *, int> m_docStatesIndices; + QVector<QVector<int>> m_transitionsForState; + + int m_currentTransition = StateTable::InvalidIndex; + bool m_bindLate = false; + QVector<DocumentModel::DataElement *> m_dataElements; + Table<QStringList, QString, int> m_stateNames; +}; + +} // anonymous namespace QScxmlTableData::~QScxmlTableData() {} -QT_END_NAMESPACE +void GeneratedTableData::build(DocumentModel::ScxmlDocument *doc, + GeneratedTableData *table, + MetaDataInfo *metaDataInfo, + DataModelInfo *dataModelInfo, + GeneratedTableData::CreateFactoryId func) +{ + TableDataBuilder builder(*table, *metaDataInfo, *dataModelInfo, func); + builder.buildTableData(doc); +} + +QString GeneratedTableData::toString(const int *stateMachineTable) +{ + QString result; + QTextStream out(&result); + + const StateTable *st = reinterpret_cast<const StateTable *>(stateMachineTable); + + out << "{" << endl + << "\t0x" << hex << st->version << dec << ", // version" << endl + << "\t" << st->name << ", // name" << endl + << "\t" << st->dataModel << ", // data-model" << endl + << "\t" << st->childStates << ", // child states array offset" << endl + << "\t" << st->initialTransition << ", // transition to initial states" << endl + << "\t" << st->initialSetup << ", // initial setup" << endl + << "\t" << st->binding << ", // binding" << endl + << "\t" << st->maxServiceId << ", // maxServiceId" << endl + << "\t" << st->stateOffset << ", " << st->stateCount + << ", // state offset and count" << endl + << "\t" << st->transitionOffset << ", " << st->transitionCount + << ", // transition offset and count" << endl + << "\t" << st->arrayOffset << ", " << st->arraySize << ", // array offset and size" << endl + << endl; + + out << "\t// States:" << endl; + for (int i = 0; i < st->stateCount; ++i) { + const StateTable::State &s = st->state(i); + out << "\t" + << s.name << ", " + << s.parent << ", " + << s.type << ", " + << s.initialTransition << ", " + << s.initInstructions << ", " + << s.entryInstructions << ", " + << s.exitInstructions << ", " + << s.doneData << ", " + << s.childStates << ", " + << s.transitions << ", " + << s.serviceFactoryIds << "," + << endl; + } + + out << endl + << "\t// Transitions:" << endl; + for (int i = 0; i < st->transitionCount; ++i) { + auto t = st->transition(i); + out << "\t" + << t.events << ", " + << t.condition << ", " + << t.type << ", " + << t.source << ", " + << t.targets << ", " + << t.transitionInstructions << ", " + << endl ; + } + + out << endl + << "\t// Arrays:" << endl; + int nextStart = 0; + while (nextStart < st->arraySize) { + const StateTable::Array a = st->array(nextStart); + out << "\t" << a.size() << ", "; + for (int j = 0; j < a.size(); ++j) { + out << a[j] << ", "; + } + out << endl; + nextStart += a.size() + 1; + } + + out << hex; + out << endl + << "\t0x" << StateTable::terminator << " // terminator" << endl + << "}"; + + return result; +} + +QString GeneratedTableData::string(StringId id) const +{ + return id == NoString ? QString() : theStrings.at(id); +} + +InstructionId *GeneratedTableData::instructions() const +{ + return const_cast<InstructionId *>(theInstructions.data()); +} + +EvaluatorInfo GeneratedTableData::evaluatorInfo(EvaluatorId evaluatorId) const +{ + return theEvaluators[evaluatorId]; +} + +AssignmentInfo GeneratedTableData::assignmentInfo(EvaluatorId assignmentId) const +{ + return theAssignments[assignmentId]; +} + +ForeachInfo GeneratedTableData::foreachInfo(EvaluatorId foreachId) const +{ + return theForeaches[foreachId]; +} + +StringId *GeneratedTableData::dataNames(int *count) const +{ + Q_ASSERT(count); + *count = theDataNameIds.size(); + return const_cast<StringId *>(theDataNameIds.data()); +} + +ContainerId GeneratedTableData::initialSetup() const +{ + return theInitialSetup; +} + +QString GeneratedTableData::name() const +{ + return string(theName); +} + +const qint32 *GeneratedTableData::stateMachineTable() const +{ + return theStateMachineTable.constData(); +} + +QScxmlInvokableServiceFactory *GeneratedTableData::serviceFactory(int id) const +{ + Q_UNUSED(id); + return nullptr; +} diff --git a/src/scxml/qscxmltabledata.h b/src/scxml/qscxmltabledata.h index 59fb105..5fcabd1 100644 --- a/src/scxml/qscxmltabledata.h +++ b/src/scxml/qscxmltabledata.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QSCXMLTABLEDATA_P -#define QSCXMLTABLEDATA_P +#ifndef QSCXMLTABLEDATA_H +#define QSCXMLTABLEDATA_H #include <QtScxml/qscxmlexecutablecontent.h> #include <QString> @@ -49,13 +49,15 @@ QT_BEGIN_NAMESPACE +class QScxmlInvokableServiceFactory; + class Q_SCXML_EXPORT QScxmlTableData { public: virtual ~QScxmlTableData(); virtual QString string(QScxmlExecutableContent::StringId id) const = 0; - virtual QScxmlExecutableContent::Instructions instructions() const = 0; + virtual QScxmlExecutableContent::InstructionId *instructions() const = 0; virtual QScxmlExecutableContent::EvaluatorInfo evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const = 0; virtual QScxmlExecutableContent::AssignmentInfo assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const = 0; virtual QScxmlExecutableContent::ForeachInfo foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const = 0; @@ -63,8 +65,11 @@ public: virtual QScxmlExecutableContent::ContainerId initialSetup() const = 0; virtual QString name() const = 0; + + virtual const qint32 *stateMachineTable() const = 0; + virtual QScxmlInvokableServiceFactory *serviceFactory(int id) const = 0; }; QT_END_NAMESPACE -#endif // QSCXMLTABLEDATA_P +#endif // QSCXMLTABLEDATA_H diff --git a/src/scxml/qscxmltabledata_p.h b/src/scxml/qscxmltabledata_p.h new file mode 100644 index 0000000..2c825db --- /dev/null +++ b/src/scxml/qscxmltabledata_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCXMLTABLEDATA_P_H +#define QSCXMLTABLEDATA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtScxml/qscxmltabledata.h> +#include <QtCore/qsharedpointer.h> +#include <QtCore/qhash.h> +#include <QtCore/qvector.h> + +#include <functional> + +QT_BEGIN_NAMESPACE +class QTextStream; +class QScxmlInvokableServiceFactory; + +namespace DocumentModel { +struct ScxmlDocument; +} + +namespace QScxmlInternal { +class Q_SCXML_EXPORT GeneratedTableData: public QScxmlTableData +{ +public: + typedef std::function< + int(const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &namelist, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶ms, + QSharedPointer<DocumentModel::ScxmlDocument> content) + > CreateFactoryId; + + struct MetaDataInfo { + QStringList stateNames; + }; + + struct DataModelInfo { + QHash<QScxmlExecutableContent::EvaluatorId, QString> stringEvaluators; + QHash<QScxmlExecutableContent::EvaluatorId, QString> boolEvaluators; + QHash<QScxmlExecutableContent::EvaluatorId, QString> variantEvaluators; + QHash<QScxmlExecutableContent::EvaluatorId, QString> voidEvaluators; + }; + +public: + static void build(DocumentModel::ScxmlDocument *doc, GeneratedTableData *table, + MetaDataInfo *metaDataInfo, DataModelInfo *dataModelInfo, + CreateFactoryId func); + static QString toString(const int *stateMachineTable); + +public: + QString string(QScxmlExecutableContent::StringId id) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::InstructionId *instructions() const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::EvaluatorInfo evaluatorInfo( + QScxmlExecutableContent::EvaluatorId evaluatorId) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::AssignmentInfo assignmentInfo( + QScxmlExecutableContent::EvaluatorId assignmentId) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::ForeachInfo foreachInfo( + QScxmlExecutableContent::EvaluatorId foreachId) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::StringId *dataNames(int *count) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL; + QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL; + const qint32 *stateMachineTable() const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlInvokableServiceFactory *serviceFactory(int id) const Q_DECL_OVERRIDE; + +public: + QVector<qint32> theStateMachineTable; + QStringList theStrings; + QVector<qint32> theInstructions; + QVector<QScxmlExecutableContent::EvaluatorInfo> theEvaluators; + QVector<QScxmlExecutableContent::AssignmentInfo> theAssignments; + QVector<QScxmlExecutableContent::ForeachInfo> theForeaches; + QVector<QScxmlExecutableContent::StringId> theDataNameIds; + QScxmlExecutableContent::EvaluatorId theInitialSetup; + int theName; +}; +} // QScxmlInternal namespace + +QT_END_NAMESPACE + +#endif // QSCXMLTABLEDATA_P_H diff --git a/src/scxml/scxml.pro b/src/scxml/scxml.pro index 40601e3..1f5bc25 100644 --- a/src/scxml/scxml.pro +++ b/src/scxml/scxml.pro @@ -1,6 +1,6 @@ TARGET = QtScxml QT = core-private qml-private -MODULE_CONFIG += c++11 +MODULE_CONFIG += c++11 qscxmlc load(qt_module) @@ -10,8 +10,8 @@ CONFIG += $$MODULE_CONFIG DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII HEADERS += \ - qscxmlparser.h \ - qscxmlparser_p.h \ + qscxmlcompiler.h \ + qscxmlcompiler_p.h \ qscxmlstatemachine.h \ qscxmlstatemachine_p.h \ qscxmlglobals.h \ @@ -25,16 +25,17 @@ HEADERS += \ qscxmlevent_p.h \ qscxmldatamodel.h \ qscxmldatamodel_p.h \ - qscxmlqstates.h \ - qscxmlqstates_p.h \ qscxmlcppdatamodel_p.h \ qscxmlcppdatamodel.h \ qscxmlerror.h \ + qscxmlinvokableservice_p.h \ qscxmlinvokableservice.h \ - qscxmltabledata.h + qscxmltabledata.h \ + qscxmltabledata_p.h \ + qscxmlstatemachineinfo_p.h SOURCES += \ - qscxmlparser.cpp \ + qscxmlcompiler.cpp \ qscxmlstatemachine.cpp \ qscxmlnulldatamodel.cpp \ qscxmlecmascriptdatamodel.cpp \ @@ -42,11 +43,11 @@ SOURCES += \ qscxmlexecutablecontent.cpp \ qscxmlevent.cpp \ qscxmldatamodel.cpp \ - qscxmlqstates.cpp \ qscxmlcppdatamodel.cpp \ qscxmlerror.cpp \ qscxmlinvokableservice.cpp \ - qscxmltabledata.cpp + qscxmltabledata.cpp \ + qscxmlstatemachineinfo.cpp FEATURES += ../../mkspecs/features/qscxmlc.prf features.files = $$FEATURES diff --git a/tests/3rdparty/scion-tests/scxml-test-framework/test/content-expr-in-send/test0.txml.json b/tests/3rdparty/scion-tests/scxml-test-framework/test/content-expr-in-send/test0.txml.json new file mode 100644 index 0000000..2b697c0 --- /dev/null +++ b/tests/3rdparty/scion-tests/scxml-test-framework/test/content-expr-in-send/test0.txml.json @@ -0,0 +1,4 @@ +{ + "initialConfiguration" : ["pass"], + "events" : [] +} diff --git a/tests/3rdparty/scion-tests/scxml-test-framework/test/content-expr-in-send/test0.txml.scxml b/tests/3rdparty/scion-tests/scxml-test-framework/test/content-expr-in-send/test0.txml.scxml new file mode 100644 index 0000000..4cfa34a --- /dev/null +++ b/tests/3rdparty/scion-tests/scxml-test-framework/test/content-expr-in-send/test0.txml.scxml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" binding="early" + name="content-expr-in-send" datamodel="ecmascript"> + <state id="top"> + <onentry> + <send event="timeout" delay="2s"/> + <send event="to_second"> + <content>blah</content> + </send> + </onentry> + <state id="first"> + <transition event="to_second" target="second"/> + </state> + + <state id="second"> + <onentry> + <send event="to_pass"> + <content expr="_event.data"/> + </send> + </onentry> + </state> + <transition event="to_pass" cond="_event.data=='blah'" target="pass"/> + <transition event="timeout" target="fail"/> + </state> + + <final id="pass"><onentry><log label="Outcome" expr="'pass'"/></onentry></final> + <final id="fail"><onentry><log label="Outcome" expr="'fail'"/></onentry></final> +</scxml> diff --git a/tests/3rdparty/scion-tests/scxml-test-framework/test/test216sub1.scxml b/tests/3rdparty/scion-tests/scxml-test-framework/test/test216sub1.scxml new file mode 100644 index 0000000..cf9a3c4 --- /dev/null +++ b/tests/3rdparty/scion-tests/scxml-test-framework/test/test216sub1.scxml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- when invoked, terminate returning done.invoke. This proves that the invocation succeeded. --><scxml xmlns="http://www.w3.org/2005/07/scxml" xmlns:conf="http://www.w3.org/2005/scxml-conformance" initial="final" version="1.0" datamodel="ecmascript"> + +<final id="final"/> + +</scxml> diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index a6dd3ae..60576d5 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -3,6 +3,6 @@ SUBDIRS = cmake\ compiled\ dynamicmetaobject\ parser\ - qscxmlc\ scion\ - statemachine + statemachine \ + statemachineinfo diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index 8518d09..9c9e098 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -11,3 +11,5 @@ include("${_Qt5CTestMacros}") test_module_includes( Scxml QScxmlEvent ) + +expect_pass(test_qtscxml_module) diff --git a/tests/auto/cmake/test_qtscxml_module/CMakeLists.txt b/tests/auto/cmake/test_qtscxml_module/CMakeLists.txt new file mode 100644 index 0000000..e2cf03a --- /dev/null +++ b/tests/auto/cmake/test_qtscxml_module/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.8) + +project(test_qtscxml_module) + +find_package(Qt5Scxml REQUIRED) + +set(MAIN_SRCS main.cpp) +qt5_add_statecharts(MAIN_SRCS + ../../compiled/connection.scxml + + # unused, just for testing whether it's possible to pass multiple files + ../../compiled/topmachine.scxml +) +add_executable(mainapp ${MAIN_SRCS}) +target_include_directories(mainapp PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(mainapp Qt5::Scxml) diff --git a/tests/auto/cmake/test_qtscxml_module/main.cpp b/tests/auto/cmake/test_qtscxml_module/main.cpp new file mode 100644 index 0000000..2d081df --- /dev/null +++ b/tests/auto/cmake/test_qtscxml_module/main.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtScxml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "connection.h" + +int main(int argc, char **argv) +{ + Q_UNUSED(argc); + Q_UNUSED(argv); + + Connection connection; + Q_UNUSED(connection); + return 0; +} diff --git a/tests/auto/compiled/anonymousstate.scxml b/tests/auto/compiled/anonymousstate.scxml index 016b339..60adfb0 100644 --- a/tests/auto/compiled/anonymousstate.scxml +++ b/tests/auto/compiled/anonymousstate.scxml @@ -1,5 +1,4 @@ <?xml version="1.0" ?> -<!-- enable-qt-mode: no --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="Pinball2" datamodel="ecmascript"> <state> diff --git a/tests/auto/compiled/compiled.pro b/tests/auto/compiled/compiled.pro index 0fd3db9..713f484 100644 --- a/tests/auto/compiled/compiled.pro +++ b/tests/auto/compiled/compiled.pro @@ -18,6 +18,8 @@ STATECHARTS = \ anonymousstate.scxml \ submachineunicodename.scxml \ datainnulldatamodel.scxml \ - initialhistory.scxml + initialhistory.scxml \ + connection.scxml \ + topmachine.scxml -load(qscxmlc) +RESOURCES = tst_compiled.qrc diff --git a/tests/auto/parser/data/qtmode.scxml b/tests/auto/compiled/connection.scxml index c5df8b0..c5654e5 100644 --- a/tests/auto/parser/data/qtmode.scxml +++ b/tests/auto/compiled/connection.scxml @@ -28,19 +28,15 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="test1"> - <state id="a"> - <transition event="a"/> - <transition event="void"/> - <transition event="aChanged"/> - <transition event="finished"/> - <transition event="done.state.a"/> - <transition event="done.state.double"/> - </state> - <state id="int"/> - <state id="objectName"/> - <state id="foo"/> - <state id="fooChanged"/> + name="Connection"> + <parallel id="top"> + <state id="a"> + <state id="a1"/> + <state id="a2"/> + </state> + <state id="b"> + <final id="final"/> + </state> + </parallel> </scxml> diff --git a/tests/auto/compiled/eventnames1.scxml b/tests/auto/compiled/eventnames1.scxml index 150f6c0..475b1b0 100644 --- a/tests/auto/compiled/eventnames1.scxml +++ b/tests/auto/compiled/eventnames1.scxml @@ -28,7 +28,7 @@ ** ****************************************************************************/ --> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="eventnames1"> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="eventnames1" initial="a"> <state id="a"> <onentry> <send event="valid"/> @@ -42,5 +42,8 @@ <transition event="-valid-"/> <transition event="also valid"/> <transition event="ÿvalid"/> + <transition event="näl" target="b"/> + </state> + <state id="b"> </state> </scxml> diff --git a/tests/auto/compiled/eventnames2.scxml b/tests/auto/compiled/eventnames2.scxml index 32c80d5..49a5e98 100644 --- a/tests/auto/compiled/eventnames2.scxml +++ b/tests/auto/compiled/eventnames2.scxml @@ -28,7 +28,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="eventnames2"> <state id="a"> <onentry> diff --git a/tests/auto/compiled/submachineA.scxml b/tests/auto/compiled/submachineA.scxml new file mode 100644 index 0000000..0924b2e --- /dev/null +++ b/tests/auto/compiled/submachineA.scxml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="SubMachine"> + <final id="topState"/> +</scxml> diff --git a/tests/auto/compiled/submachineB.scxml b/tests/auto/compiled/submachineB.scxml new file mode 100644 index 0000000..0924b2e --- /dev/null +++ b/tests/auto/compiled/submachineB.scxml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="SubMachine"> + <final id="topState"/> +</scxml> diff --git a/tests/auto/compiled/submachineunicodename.scxml b/tests/auto/compiled/submachineunicodename.scxml index b62e751..9cee0e3 100644 --- a/tests/auto/compiled/submachineunicodename.scxml +++ b/tests/auto/compiled/submachineunicodename.scxml @@ -1,5 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" diff --git a/tests/auto/compiled/topmachine.scxml b/tests/auto/compiled/topmachine.scxml new file mode 100644 index 0000000..b733c6a --- /dev/null +++ b/tests/auto/compiled/topmachine.scxml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="TopMachine" datamodel="ecmascript"> + <datamodel> + <data id="doneCounter" expr="0"/> + </datamodel> + <state id="topState"> + <invoke type="scxml" id="submachine.1" src="file:submachineA.scxml"/> + <invoke type="scxml" id="submachine.2" src="file:submachineA.scxml"/> + <invoke type="scxml" id="submachine.3" src="file:submachineB.scxml"/> + <transition event="done.invoke"> + <assign location="doneCounter" expr="doneCounter + 1"/> + <if cond="doneCounter === 3"> + <send event="goToFinal" delay="1s"/> + </if> + </transition> + <transition event="goToFinal" target="finalState"/> + </state> + <final id="finalState"/> +</scxml> diff --git a/tests/auto/compiled/tst_compiled.cpp b/tests/auto/compiled/tst_compiled.cpp index 49174e3..ab590f7 100644 --- a/tests/auto/compiled/tst_compiled.cpp +++ b/tests/auto/compiled/tst_compiled.cpp @@ -29,12 +29,16 @@ #include <QtTest> #include <QObject> #include <QXmlStreamReader> -#include <QtScxml/qscxmlparser.h> +#include <QtScxml/qscxmlcompiler.h> #include <QtScxml/qscxmlstatemachine.h> +#include <QtScxml/qscxmlinvokableservice.h> #include "ids1.h" #include "statemachineunicodename.h" #include "datainnulldatamodel.h" #include "submachineunicodename.h" +#include "eventnames1.h" +#include "connection.h" +#include "topmachine.h" Q_DECLARE_METATYPE(QScxmlError); @@ -48,27 +52,34 @@ private Q_SLOTS: void stateNames(); void nullDataInit(); void subMachineUnicodeName(); + void unicodeEventName(); + void connection(); + void myConnection(); + void topMachine(); + void topMachineDynamic(); + void publicSignals(); }; void tst_Compiled::stateNames() { ids1 stateMachine; + // The states have to be appear in document order: QStringList ids1States({ - "_", - "_VALID", - "__valid", - "foo-bar", "foo.bar", + "foo-bar", "foo_bar", - "n_0xe4_l", + "_", "näl", + "n_0xe4_l", + "_VALID", + "__valid", "qÿ̀i", }); QCOMPARE(stateMachine.stateNames(false), ids1States); - foreach (const QString &state, ids1States) { + for (const QString &state : qAsConst(ids1States)) { QVariant prop = stateMachine.property(state.toUtf8().constData()); QVERIFY(!prop.isNull()); QVERIFY(prop.isValid()); @@ -94,10 +105,207 @@ void tst_Compiled::nullDataInit() void tst_Compiled::subMachineUnicodeName() { Directions1 directions; - QVERIFY(directions.init()); - QVariant prop = directions.property("änywhere"); - QVERIFY(!prop.isNull()); - QVERIFY(prop.isValid()); + QSignalSpy stableStateSpy(&directions, SIGNAL(reachedStableState())); + directions.start(); + stableStateSpy.wait(5000); + QScxmlInvokableService *service = directions.invokedServices().value(0); + QVERIFY(service); + QCOMPARE(service->name(), QString("änywhere")); +} + +void tst_Compiled::unicodeEventName() +{ + eventnames1 names; + QSignalSpy stableStateSpy(&names, SIGNAL(reachedStableState())); + names.start(); + + stableStateSpy.wait(5000); + + QCOMPARE(names.activeStateNames(), QStringList(QLatin1String("a"))); + names.submitEvent("näl"); + stableStateSpy.wait(5000); + QCOMPARE(names.activeStateNames(), QStringList(QLatin1String("b"))); +} + +class Receiver : public QObject +{ + Q_OBJECT +public slots: + void receive(bool enabled) + { + received = received || enabled; + } + + void enter() + { + entered = true; + } + + void exit() + { + exited = true; + } + +public: + bool received = false; + bool entered = false; + bool exited = false; +}; + +void tst_Compiled::connection() +{ + Connection stateMachine; + + Receiver receiverA; + Receiver receiverA1; + Receiver receiverA2; + Receiver receiverB; + Receiver receiverFinal; + + QMetaObject::Connection conA = stateMachine.connectToState("a", &receiverA, SLOT(receive(bool))); + QMetaObject::Connection conA1 = stateMachine.connectToState("a1", &receiverA1, SLOT(receive(bool))); + QMetaObject::Connection conA2 = stateMachine.connectToState("a2", &receiverA2, SLOT(receive(bool))); + QMetaObject::Connection conB = stateMachine.connectToState("b", &receiverB, SLOT(receive(bool))); + QMetaObject::Connection conFinal = stateMachine.connectToState("final", &receiverFinal, SLOT(receive(bool))); + + typedef QScxmlStateMachine QXSM; + QMetaObject::Connection aEntry = stateMachine.connectToState("a", QXSM::onEntry(&receiverA, "enter")); + QMetaObject::Connection aExit = stateMachine.connectToState("a", QXSM::onExit(&receiverA, "exit")); + + QVERIFY(aEntry); + QVERIFY(aExit); + + QVERIFY(conA); + QVERIFY(conA1); + QVERIFY(conA2); + QVERIFY(conB); + QVERIFY(conFinal); + + stateMachine.start(); + + QTRY_VERIFY(receiverA.received); + QTRY_VERIFY(receiverA1.received); + QTRY_VERIFY(!receiverA2.received); + QTRY_VERIFY(receiverB.received); + QTRY_VERIFY(receiverFinal.received); + + QVERIFY(disconnect(conA)); + QVERIFY(disconnect(conA1)); + QVERIFY(disconnect(conA2)); + QVERIFY(disconnect(conB)); + QVERIFY(disconnect(conFinal)); + +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + QVERIFY(receiverA.entered); + QVERIFY(!receiverA.exited); + QVERIFY(disconnect(aEntry)); + QVERIFY(disconnect(aExit)); +#endif +} + +class MyConnection : public Connection +{ + Q_OBJECT +public: + MyConnection(QObject *parent = 0) + : Connection(parent) + {} +}; + +void tst_Compiled::myConnection() +{ + MyConnection stateMachine; + + Receiver receiverA; + Receiver receiverA1; + Receiver receiverA2; + Receiver receiverB; + Receiver receiverFinal; + + QMetaObject::Connection conA = stateMachine.connectToState("a", &receiverA, SLOT(receive(bool))); + QMetaObject::Connection conA1 = stateMachine.connectToState("a1", &receiverA1, SLOT(receive(bool))); + QMetaObject::Connection conA2 = stateMachine.connectToState("a2", &receiverA2, SLOT(receive(bool))); + QMetaObject::Connection conB = stateMachine.connectToState("b", &receiverB, SLOT(receive(bool))); + QMetaObject::Connection conFinal = stateMachine.connectToState("final", &receiverFinal, SLOT(receive(bool))); + + QVERIFY(conA); + QVERIFY(conA1); + QVERIFY(conA2); + QVERIFY(conB); + QVERIFY(conFinal); + + stateMachine.start(); + + QTRY_VERIFY(receiverA.received); + QTRY_VERIFY(receiverA1.received); + QTRY_VERIFY(!receiverA2.received); + QTRY_VERIFY(receiverB.received); + QTRY_VERIFY(receiverFinal.received); + + QVERIFY(disconnect(conA)); + QVERIFY(disconnect(conA1)); + QVERIFY(disconnect(conA2)); + QVERIFY(disconnect(conB)); + QVERIFY(disconnect(conFinal)); +} + +void tst_Compiled::topMachine() +{ + TopMachine stateMachine; + int doneCounter = 0; + int invokableServicesCount = 0; + + stateMachine.connectToEvent("done.invoke.submachine", [&doneCounter](const QScxmlEvent &) { + ++doneCounter; + }); + + QObject::connect(&stateMachine, &QScxmlStateMachine::invokedServicesChanged, + [&invokableServicesCount](const QVector<QScxmlInvokableService *> &services) { + invokableServicesCount = services.count(); + }); + + stateMachine.start(); + + QTRY_COMPARE(invokableServicesCount, 3); + QTRY_COMPARE(doneCounter, 3); + QCOMPARE(stateMachine.invokedServices().count(), 3); + QTRY_COMPARE(invokableServicesCount, 0); +} + +void tst_Compiled::topMachineDynamic() +{ + QScopedPointer<QScxmlStateMachine> stateMachine( + QScxmlStateMachine::fromFile(QString(":/topmachine.scxml"))); + QVERIFY(!stateMachine.isNull()); + int doneCounter = 0; + int invokableServicesCount = 0; + + stateMachine->connectToEvent("done.invoke.submachine", [&doneCounter](const QScxmlEvent &) { + ++doneCounter; + }); + + QObject::connect(stateMachine.data(), &QScxmlStateMachine::invokedServicesChanged, + [&invokableServicesCount](const QVector<QScxmlInvokableService *> &services) { + invokableServicesCount = services.count(); + }); + + stateMachine->start(); + + QTRY_COMPARE(invokableServicesCount, 3); + QTRY_COMPARE(doneCounter, 3); + QCOMPARE(stateMachine->invokedServices().count(), 3); + QTRY_COMPARE(invokableServicesCount, 0); +} + +void tst_Compiled::publicSignals() +{ + const QMetaObject *connectionMeta = &Connection::staticMetaObject; + int index = connectionMeta->indexOfSignal("aChanged(bool)"); + QVERIFY(index >= 0); + + QMetaMethod aChanged = connectionMeta->method(index); + QVERIFY(aChanged.isValid()); + QCOMPARE(aChanged.access(), QMetaMethod::Public); } QTEST_MAIN(tst_Compiled) diff --git a/tests/auto/compiled/tst_compiled.qrc b/tests/auto/compiled/tst_compiled.qrc new file mode 100644 index 0000000..43d355c --- /dev/null +++ b/tests/auto/compiled/tst_compiled.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file>topmachine.scxml</file> + <file>submachineA.scxml</file> + <file>submachineB.scxml</file> + </qresource> +</RCC> diff --git a/tests/auto/dynamicmetaobject/dynamicmetaobject.pro b/tests/auto/dynamicmetaobject/dynamicmetaobject.pro index f78e5ac..5ec4e5f 100644 --- a/tests/auto/dynamicmetaobject/dynamicmetaobject.pro +++ b/tests/auto/dynamicmetaobject/dynamicmetaobject.pro @@ -11,5 +11,3 @@ RESOURCES += tst_dynamicmetaobject.qrc SOURCES += \ tst_dynamicmetaobject.cpp - -load(qscxmlc) diff --git a/tests/auto/dynamicmetaobject/mediaplayer.scxml b/tests/auto/dynamicmetaobject/mediaplayer.scxml index 025245a..ca68039 100644 --- a/tests/auto/dynamicmetaobject/mediaplayer.scxml +++ b/tests/auto/dynamicmetaobject/mediaplayer.scxml @@ -50,7 +50,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" @@ -76,13 +75,13 @@ <state id="playing"> <onentry> <assign location="media" expr="_event.data.media"/> - <send type="qt:signal" event="playbackStarted"> + <send event="playbackStarted"> <param name="media" expr="media"/> </send> </onentry> <onexit> - <send type="qt:signal" event="playbackStopped"> + <send event="playbackStopped"> <param name="media" expr="media"/> </send> </onexit> diff --git a/tests/auto/dynamicmetaobject/test1.scxml b/tests/auto/dynamicmetaobject/test1.scxml index 72fd592..531b1d5 100644 --- a/tests/auto/dynamicmetaobject/test1.scxml +++ b/tests/auto/dynamicmetaobject/test1.scxml @@ -32,6 +32,7 @@ name="test1" datamodel="ecmascript"> <state id="a"> <transition event="foo"/> + <transition event="näl" target="b"/> </state> <state id="b"> <transition event="foo"/> diff --git a/tests/auto/dynamicmetaobject/tst_dynamicmetaobject.cpp b/tests/auto/dynamicmetaobject/tst_dynamicmetaobject.cpp index 7610dd7..83fceeb 100644 --- a/tests/auto/dynamicmetaobject/tst_dynamicmetaobject.cpp +++ b/tests/auto/dynamicmetaobject/tst_dynamicmetaobject.cpp @@ -44,41 +44,28 @@ void tst_DynamicMetaObject::dynamicPartCheck_data() { QTest::addColumn<QString>("scxmlFileName"); QTest::addColumn<QStringList>("expectedProperties"); - QTest::addColumn<QStringList>("expectedMethods"); QTest::addColumn<QStringList>("expectedSignals"); - QTest::addColumn<QStringList>("expectedSlots"); { // test1.scxml const QStringList expectedProperties = { QLatin1String("a"), QLatin1String("b") }; - const QStringList expectedMethods = { }; const QStringList expectedSignals = { QLatin1String("aChanged(bool)"), QLatin1String("bChanged(bool)") }; - const QStringList expectedSlots = { }; QTest::newRow("test1") << QString(":/tst_dynamicmetaobject/test1.scxml") << expectedProperties - << expectedMethods - << expectedSignals - << expectedSlots; + << expectedSignals; } { // mediaplayer.scxml const QStringList expectedProperties = { QLatin1String("stopped"), QLatin1String("playing") }; - const QStringList expectedMethods = { }; const QStringList expectedSignals = { QLatin1String("stoppedChanged(bool)"), - QLatin1String("playingChanged(bool)"), - QLatin1String("playbackStarted(QVariant)"), - QLatin1String("playbackStopped(QVariant)") }; - const QStringList expectedSlots = { QLatin1String("tap()"), - QLatin1String("tap(QVariant)") }; + QLatin1String("playingChanged(bool)") }; QTest::newRow("mediaplayer") << QString(":/tst_dynamicmetaobject/mediaplayer.scxml") << expectedProperties - << expectedMethods - << expectedSignals - << expectedSlots; + << expectedSignals; } } @@ -86,9 +73,7 @@ void tst_DynamicMetaObject::dynamicPartCheck() { QFETCH(QString, scxmlFileName); QFETCH(QStringList, expectedProperties); - QFETCH(QStringList, expectedMethods); QFETCH(QStringList, expectedSignals); - QFETCH(QStringList, expectedSlots); QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(scxmlFileName)); QVERIFY(!stateMachine.isNull()); @@ -98,42 +83,25 @@ void tst_DynamicMetaObject::dynamicPartCheck() QStringList dynamicProperties; for (int i = metaObject->propertyOffset(); i < metaObject->propertyCount(); i++) { - QMetaProperty metaProperty = metaObject->property(i); + const QMetaProperty metaProperty = metaObject->property(i); dynamicProperties << metaProperty.name(); } - QStringList dynamicMethods, dynamicSignals, dynamicSlots; + QStringList dynamicSignals; for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); i++) { - QMetaMethod metaMethod = metaObject->method(i); - switch (metaMethod.methodType()) { - case QMetaMethod::Method: - dynamicMethods << metaMethod.methodSignature(); - break; - case QMetaMethod::Signal: + const QMetaMethod metaMethod = metaObject->method(i); + if (metaMethod.methodType() == QMetaMethod::Signal) dynamicSignals << metaMethod.methodSignature(); - break; - case QMetaMethod::Slot: - dynamicSlots << metaMethod.methodSignature(); - break; - default: - break; - } } // TODO: remove sorting when we drop QSet internally expectedProperties.sort(); - expectedMethods.sort(); expectedSignals.sort(); - expectedSlots.sort(); dynamicProperties.sort(); - dynamicMethods.sort(); dynamicSignals.sort(); - dynamicSlots.sort(); QCOMPARE(dynamicProperties, expectedProperties); - QCOMPARE(dynamicMethods, expectedMethods); QCOMPARE(dynamicSignals, expectedSignals); - QCOMPARE(dynamicSlots, expectedSlots); } QTEST_MAIN(tst_DynamicMetaObject) diff --git a/tests/auto/parser/data/badInitial.scxml b/tests/auto/parser/data/badInitial.scxml new file mode 100644 index 0000000..8247857 --- /dev/null +++ b/tests/auto/parser/data/badInitial.scxml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" + initial="stopped" +> + <script> + <initial> + + <state id="stopped"> + </state> +</scxml> diff --git a/tests/auto/parser/data/badInitial.scxml.errors b/tests/auto/parser/data/badInitial.scxml.errors new file mode 100644 index 0000000..fa2eba9 --- /dev/null +++ b/tests/auto/parser/data/badInitial.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/badInitial.scxml:8:13: error: Unexpected element initial +:/tst_parser/data/badInitial.scxml:12:8: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/qscxmlc/data/commentInScript.scxml b/tests/auto/parser/data/commentInScript.scxml index 2af8286..2a663b1 100644 --- a/tests/auto/qscxmlc/data/commentInScript.scxml +++ b/tests/auto/parser/data/commentInScript.scxml @@ -1,15 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="CommentInScript" - datamodel="ecmascript" > - <datamodel> - <data id="media"/> - </datamodel> - <script><!-- function isValidMedia() { var m = _event.data.media diff --git a/tests/auto/parser/data/commentInScript.scxml.errors b/tests/auto/parser/data/commentInScript.scxml.errors new file mode 100644 index 0000000..d12133f --- /dev/null +++ b/tests/auto/parser/data/commentInScript.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/commentInScript.scxml:6:0: error: Error parsing SCXML file: Premature end of document. diff --git a/tests/auto/qscxmlc/data/empty.scxml b/tests/auto/parser/data/empty.scxml index 10c563b..10c563b 100644 --- a/tests/auto/qscxmlc/data/empty.scxml +++ b/tests/auto/parser/data/empty.scxml diff --git a/tests/auto/parser/data/empty.scxml.errors b/tests/auto/parser/data/empty.scxml.errors new file mode 100644 index 0000000..ce558e0 --- /dev/null +++ b/tests/auto/parser/data/empty.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/empty.scxml:2:0: error: Missing root element diff --git a/tests/auto/parser/data/eventnames.scxml.errors b/tests/auto/parser/data/eventnames.scxml.errors new file mode 100644 index 0000000..9b4d5de --- /dev/null +++ b/tests/auto/parser/data/eventnames.scxml.errors @@ -0,0 +1,5 @@ +:/tst_parser/data/eventnames.scxml:50:38: error: '.invalid' is not a valid event +:/tst_parser/data/eventnames.scxml:51:38: error: 'invalid.' is not a valid event +:/tst_parser/data/eventnames.scxml:39:36: error: '.invalid' is not a valid event +:/tst_parser/data/eventnames.scxml:40:36: error: 'invalid.' is not a valid event +:/tst_parser/data/eventnames.scxml:41:36: error: 'in valid' is not a valid event diff --git a/tests/auto/parser/data/ids1.scxml.errors b/tests/auto/parser/data/ids1.scxml.errors new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/auto/parser/data/ids1.scxml.errors diff --git a/tests/auto/parser/data/ids2.scxml b/tests/auto/parser/data/ids2.scxml index 228b487..5378fcb 100644 --- a/tests/auto/parser/data/ids2.scxml +++ b/tests/auto/parser/data/ids2.scxml @@ -28,7 +28,6 @@ ** ****************************************************************************/ --> -<!-- enable-qt-mode: yes --> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="ids2"> <state id="foo.bar"/> <state id="foo-bar"/> diff --git a/tests/auto/parser/data/ids2.scxml.errors b/tests/auto/parser/data/ids2.scxml.errors new file mode 100644 index 0000000..3ca729e --- /dev/null +++ b/tests/auto/parser/data/ids2.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/ids2.scxml:35:19: error: '1' is not a valid XML ID diff --git a/tests/auto/parser/data/invalidContent.scxml b/tests/auto/parser/data/invalidContent.scxml new file mode 100644 index 0000000..232c0ae --- /dev/null +++ b/tests/auto/parser/data/invalidContent.scxml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="somewhere"> + <invoke type="http://www.w3.org/TR/scxml/"> + <content /> + <scxml name="anywhere" version="1.0"> + <state id="here"> + </state> + </scxml> + </content> + </invoke> + </state> +</scxml> diff --git a/tests/auto/parser/data/invalidContent.scxml.errors b/tests/auto/parser/data/invalidContent.scxml.errors new file mode 100644 index 0000000..77db37e --- /dev/null +++ b/tests/auto/parser/data/invalidContent.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/invalidContent.scxml:9:53: error: Unexpected element scxml +:/tst_parser/data/invalidContent.scxml:13:22: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/invalidRoot1.scxml b/tests/auto/parser/data/invalidRoot1.scxml new file mode 100644 index 0000000..f21ad1e --- /dev/null +++ b/tests/auto/parser/data/invalidRoot1.scxml @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<Scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + </state> +</scxml> diff --git a/tests/auto/parser/data/invalidRoot1.scxml.errors b/tests/auto/parser/data/invalidRoot1.scxml.errors new file mode 100644 index 0000000..17a4705 --- /dev/null +++ b/tests/auto/parser/data/invalidRoot1.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/invalidRoot1.scxml:2:61: error: Unknown element Scxml +:/tst_parser/data/invalidRoot1.scxml:5:8: error: Missing root element diff --git a/tests/auto/parser/data/invalidRoot2.scxml b/tests/auto/parser/data/invalidRoot2.scxml new file mode 100644 index 0000000..260373f --- /dev/null +++ b/tests/auto/parser/data/invalidRoot2.scxml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="somewhere"> + <invoke type="http://www.w3.org/TR/scxml/"> + <content> + <scxmn name="anywhere" version="1.0"> + <state id="there"datamodel + <transition event="goHere" target="here"/> + </state> + </scxml> + </content> + </invoke> + </state> +</scxml> diff --git a/tests/auto/parser/data/invalidRoot2.scxml.errors b/tests/auto/parser/data/invalidRoot2.scxml.errors new file mode 100644 index 0000000..10aeada --- /dev/null +++ b/tests/auto/parser/data/invalidRoot2.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/invalidRoot2.scxml:9:53: error: Unknown element scxmn +:/tst_parser/data/invalidRoot2.scxml:10:38: error: Error parsing SCXML file: Expected '>' or '/', but got '[a-zA-Z]'. diff --git a/tests/auto/parser/data/invalidRoot3.scxml b/tests/auto/parser/data/invalidRoot3.scxml new file mode 100644 index 0000000..62dab58 --- /dev/null +++ b/tests/auto/parser/data/invalidRoot3.scxml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxma + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="anyplace"> + </state> +</scxml> diff --git a/tests/auto/parser/data/invalidRoot3.scxml.errors b/tests/auto/parser/data/invalidRoot3.scxml.errors new file mode 100644 index 0000000..dd1cf6c --- /dev/null +++ b/tests/auto/parser/data/invalidRoot3.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/invalidRoot3.scxml:5:1: error: Unknown element scxma +:/tst_parser/data/invalidRoot3.scxml:8:8: error: Missing root element diff --git a/tests/auto/parser/data/invalidRoot6.scxml b/tests/auto/parser/data/invalidRoot6.scxml new file mode 100644 index 0000000..c0633e8 --- /dev/null +++ b/tests/auto/parser/data/invalidRoot6.scxml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="somewhere"> + <invoke type="http://www.w3.org/TR/scxml/"> + <content> + |scxml name="anywhere" version="1.0"> + <state id="here"> + </state> + <state id="there"> + </state> + </scxml> + </content> + </invoke> + </state> +</scxml> diff --git a/tests/auto/parser/data/invalidRoot6.scxml.errors b/tests/auto/parser/data/invalidRoot6.scxml.errors new file mode 100644 index 0000000..3e6d92d --- /dev/null +++ b/tests/auto/parser/data/invalidRoot6.scxml.errors @@ -0,0 +1,3 @@ +:/tst_parser/data/invalidRoot6.scxml:10:37: error: Unexpected element state +:/tst_parser/data/invalidRoot6.scxml:12:38: error: Unexpected element state +:/tst_parser/data/invalidRoot6.scxml:14:24: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/invalidUnicode1.scxml b/tests/auto/parser/data/invalidUnicode1.scxml new file mode 100644 index 0000000..8e5493a --- /dev/null +++ b/tests/auto/parser/data/invalidUnicode1.scxml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="somewhere"> + <invoke type="http://www.w3.org/TR/scxml/"> + <content> + <scxml name="anywhere" version="1.0"> + <state id="here"> + </state> + </scxml> + </content> + </invoke> + </state> +</scxml> diff --git a/tests/auto/parser/data/invalidUnicode1.scxml.errors b/tests/auto/parser/data/invalidUnicode1.scxml.errors new file mode 100644 index 0000000..bde9c02 --- /dev/null +++ b/tests/auto/parser/data/invalidUnicode1.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/invalidUnicode1.scxml:7:52: error: Error parsing SCXML file: Unexpected ''. diff --git a/tests/auto/parser/data/invalidXmlHeader1.scxml b/tests/auto/parser/data/invalidXmlHeader1.scxml Binary files differnew file mode 100644 index 0000000..dc0ef36 --- /dev/null +++ b/tests/auto/parser/data/invalidXmlHeader1.scxml diff --git a/tests/auto/parser/data/invalidXmlHeader1.scxml.errors b/tests/auto/parser/data/invalidXmlHeader1.scxml.errors new file mode 100644 index 0000000..0fc223b --- /dev/null +++ b/tests/auto/parser/data/invalidXmlHeader1.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/invalidXmlHeader1.scxml:1:66: error: Missing root element diff --git a/tests/auto/parser/data/invalidXmlHeader2.scxml b/tests/auto/parser/data/invalidXmlHeader2.scxml new file mode 100644 index 0000000..a9ffeb5 --- /dev/null +++ b/tests/auto/parser/data/invalidXmlHeader2.scxml @@ -0,0 +1,5 @@ +<ry>l version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + </state> +</scxml> diff --git a/tests/auto/parser/data/invalidXmlHeader2.scxml.errors b/tests/auto/parser/data/invalidXmlHeader2.scxml.errors new file mode 100644 index 0000000..c969f8b --- /dev/null +++ b/tests/auto/parser/data/invalidXmlHeader2.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/invalidXmlHeader2.scxml:6:0: error: Missing root element diff --git a/tests/auto/qscxmlc/data/invalidstatemachinename.scxml b/tests/auto/parser/data/invalidstatemachinename.scxml index e47a699..e47a699 100644 --- a/tests/auto/qscxmlc/data/invalidstatemachinename.scxml +++ b/tests/auto/parser/data/invalidstatemachinename.scxml diff --git a/tests/auto/parser/data/invalidstatemachinename.scxml.errors b/tests/auto/parser/data/invalidstatemachinename.scxml.errors new file mode 100644 index 0000000..dbd1a34 --- /dev/null +++ b/tests/auto/parser/data/invalidstatemachinename.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/invalidstatemachinename.scxml:3:42: error: Missing root element diff --git a/tests/auto/qscxmlc/data/misplacedinvoke.scxml b/tests/auto/parser/data/misplacedinvoke.scxml index fe5e5de..fe5e5de 100644 --- a/tests/auto/qscxmlc/data/misplacedinvoke.scxml +++ b/tests/auto/parser/data/misplacedinvoke.scxml diff --git a/tests/auto/parser/data/misplacedinvoke.scxml.errors b/tests/auto/parser/data/misplacedinvoke.scxml.errors new file mode 100644 index 0000000..31c6fa0 --- /dev/null +++ b/tests/auto/parser/data/misplacedinvoke.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/misplacedinvoke.scxml:12:26: error: Unexpected element finalize diff --git a/tests/auto/parser/data/namespaces1.scxml.errors b/tests/auto/parser/data/namespaces1.scxml.errors new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/auto/parser/data/namespaces1.scxml.errors diff --git a/tests/auto/qscxmlc/data/nestedScxml.scxml b/tests/auto/parser/data/nestedScxml.scxml index 2f96347..2f96347 100644 --- a/tests/auto/qscxmlc/data/nestedScxml.scxml +++ b/tests/auto/parser/data/nestedScxml.scxml diff --git a/tests/auto/parser/data/nestedScxml.scxml.errors b/tests/auto/parser/data/nestedScxml.scxml.errors new file mode 100644 index 0000000..1735855 --- /dev/null +++ b/tests/auto/parser/data/nestedScxml.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/nestedScxml.scxml:5:60: error: Unexpected element scxml diff --git a/tests/auto/parser/data/noContentInInvoke1.scxml b/tests/auto/parser/data/noContentInInvoke1.scxml new file mode 100644 index 0000000..6d5fc1a --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke1.scxml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="somewhere"> + <invoke type="http://www.w3.org/TR/scxml/"> + <contenscxml name="anywhere" version="1.0"> + <state id="there"> + </state> + </scxml> + </content> + </invoke> + </state> +</scxml> diff --git a/tests/auto/parser/data/noContentInInvoke1.scxml.errors b/tests/auto/parser/data/noContentInInvoke1.scxml.errors new file mode 100644 index 0000000..ec7a4a4 --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke1.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/noContentInInvoke1.scxml:8:55: error: Unknown element contenscxml +:/tst_parser/data/noContentInInvoke1.scxml:11:24: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/noContentInInvoke2.scxml b/tests/auto/parser/data/noContentInInvoke2.scxml new file mode 100644 index 0000000..50e6c37 --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke2.scxml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" + initial="anyplace" +> + <state id="anyp\ace"> + <invoke type="http://www.w3.org/TR/scxml/"> + <contenon use t <sl name="anywhere version="1.0"> + <state id="here"> + </state> + </scxml> + * Redistr </content> + </invoke> + , </state> +</scxml> diff --git a/tests/auto/parser/data/noContentInInvoke2.scxml.errors b/tests/auto/parser/data/noContentInInvoke2.scxml.errors new file mode 100644 index 0000000..8fb90e5 --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke2.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/noContentInInvoke2.scxml:9:27: error: Error parsing SCXML file: Expected '=', but got '[a-zA-Z]'. diff --git a/tests/auto/parser/data/noContentInInvoke3.scxml b/tests/auto/parser/data/noContentInInvoke3.scxml Binary files differnew file mode 100644 index 0000000..918e120 --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke3.scxml diff --git a/tests/auto/parser/data/noContentInInvoke3.scxml.errors b/tests/auto/parser/data/noContentInInvoke3.scxml.errors new file mode 100644 index 0000000..e35ed7a --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke3.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/noContentInInvoke3.scxml:10:25: error: Error parsing SCXML file: Expected '=', but got '[a-zA-Z]'. diff --git a/tests/auto/parser/data/noContentInInvoke4.scxml b/tests/auto/parser/data/noContentInInvoke4.scxml new file mode 100644 index 0000000..2782daf --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke4.scxml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" + initial="anyplacE" +> + <state id="anyplace"> + <transition event="goSomewhere" target="somewhere"/> + + <state id="s* LIMITomewhere"> + <invoke type="http://www.w$.org/TR/scxml/"> + <content> + <scxml name="anywhere" version="1.0"> + <sta e id="here"> + </state> + </scxm8> + </content> + </invoke> + </state> + </state> +</scxml> diff --git a/tests/auto/parser/data/noContentInInvoke4.scxml.errors b/tests/auto/parser/data/noContentInInvoke4.scxml.errors new file mode 100644 index 0000000..cac148d --- /dev/null +++ b/tests/auto/parser/data/noContentInInvoke4.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/noContentInInvoke4.scxml:12:6: error: Error parsing SCXML file: Unexpected ''. diff --git a/tests/auto/parser/data/prematureEndOfDocument1.scxml b/tests/auto/parser/data/prematureEndOfDocument1.scxml new file mode 100644 index 0000000..63d938c --- /dev/null +++ b/tests/auto/parser/data/prematureEndOfDocument1.scxml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- enable-qt-mode: yes m-> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="anyplace"> + </state> +</scxml> diff --git a/tests/auto/parser/data/prematureEndOfDocument1.scxml.errors b/tests/auto/parser/data/prematureEndOfDocument1.scxml.errors new file mode 100644 index 0000000..24ae3e9 --- /dev/null +++ b/tests/auto/parser/data/prematureEndOfDocument1.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/prematureEndOfDocument1.scxml:2:0: error: Missing root element diff --git a/tests/auto/parser/data/prematureEndOfDocument2.scxml b/tests/auto/parser/data/prematureEndOfDocument2.scxml new file mode 100644 index 0000000..e1fde76 --- /dev/null +++ b/tests/auto/parser/data/prematureEndOfDocument2.scxml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="anyplace"> + <state id="somewhere"> + <invoke type="http://www.w3.org/TR/scxml/"> + <content> +<!-- <scxml name="anywhere" version="1.0"> + <state id="here"> + <transition event="goThere" target="there"/> + </state> + <state id="there"> + <transition event="goHere" target="here"/> + </state> + </scxml> + </content> + </invoke> + </state> + </state> +</scxml> diff --git a/tests/auto/parser/data/prematureEndOfDocument2.scxml.errors b/tests/auto/parser/data/prematureEndOfDocument2.scxml.errors new file mode 100644 index 0000000..e6532cc --- /dev/null +++ b/tests/auto/parser/data/prematureEndOfDocument2.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/prematureEndOfDocument2.scxml:10:0: error: Error parsing SCXML file: Premature end of document. diff --git a/tests/auto/parser/data/scxml1.scxml.errors b/tests/auto/parser/data/scxml1.scxml.errors new file mode 100644 index 0000000..870d167 --- /dev/null +++ b/tests/auto/parser/data/scxml1.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/scxml1.scxml:32:36: error: Unsupported data model 'foo' in scxml +:/tst_parser/data/scxml1.scxml:34:30: error: Unexpected element scxml diff --git a/tests/auto/parser/data/scxml2.scxml.errors b/tests/auto/parser/data/scxml2.scxml.errors new file mode 100644 index 0000000..a9f0e30 --- /dev/null +++ b/tests/auto/parser/data/scxml2.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/scxml2.scxml:32:34: error: Unsupperted binding type 'foo' diff --git a/tests/auto/parser/data/syntaxErrors1.scxml b/tests/auto/parser/data/syntaxErrors1.scxml new file mode 100644 index 0000000..64e25f0 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors1.scxml @@ -0,0 +1,14 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="begin"> + <transitifn event="OPER.MINUS" target="negated1" /> + </state> + + <state id="negated1"> + nameate> + </state> + <transition event="OP.INSERT"> + <assigZ location="long_expr" expr="long_expr+'+'" /> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors1.scxml.errors b/tests/auto/parser/data/syntaxErrors1.scxml.errors new file mode 100644 index 0000000..ac98603 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors1.scxml.errors @@ -0,0 +1,3 @@ +:/tst_parser/data/syntaxErrors1.scxml:4:59: error: Unknown element transitifn +:/tst_parser/data/syntaxErrors1.scxml:11:64: error: Unknown element assigZ +:/tst_parser/data/syntaxErrors1.scxml:13:12: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors10.scxml b/tests/auto/parser/data/syntaxErrors10.scxml new file mode 100644 index 0000000..257cc26 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors10.scxml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="workflow"> + <transition event="updateLights"> + |if cond="In('lightImpulseOn')"> + <raise event="dings"/> + <else/> + <raise event="turnOffGameOver"/> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors10.scxml.errors b/tests/auto/parser/data/syntaxErrors10.scxml.errors new file mode 100644 index 0000000..786d6ce --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors10.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/syntaxErrors10.scxml:7:19: error: Unexpected element else +:/tst_parser/data/syntaxErrors10.scxml:9:17: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors11.scxml b/tests/auto/parser/data/syntaxErrors11.scxml new file mode 100644 index 0000000..0ab2c58 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors11.scxml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + <transition event="OP.INSERT"> + <if cond="_event.data.operator == 'OPER.PLUS'"> + <Assign location="long_expr" expr="long_expr+'-'" /> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors11.scxml.errors b/tests/auto/parser/data/syntaxErrors11.scxml.errors new file mode 100644 index 0000000..3d34249 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors11.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/syntaxErrors11.scxml:6:68: error: Unknown element Assign diff --git a/tests/auto/parser/data/syntaxErrors12.scxml b/tests/auto/parser/data/syntaxErrors12.scxml new file mode 100644 index 0000000..9295c01 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors12.scxml @@ -0,0 +1,16 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + <transition event="OP.INSERT"> + <if cond="_event.data.operator == 'OPER.PLUS'"> + <assign location="long_expr">expr="long_expr+'+'" /> + <elseif cond="_event.data.operator=='OPER.MINUS'" /> + <assign location="long_expr" expr="long_expr+'-'" /> + <elseif cond="_event.data.operator=='OPER.STAR'" /> + <assign location="long_expr" expr="long_expr+'*'" /> + <elseif cond="_event.data.operator=='OPER.DIV'" /> + <assign location="long_expr" expr="long_expr+'/'" /> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors12.scxml.errors b/tests/auto/parser/data/syntaxErrors12.scxml.errors new file mode 100644 index 0000000..df27955 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors12.scxml.errors @@ -0,0 +1,7 @@ +:/tst_parser/data/syntaxErrors12.scxml:7:64: error: Unexpected element elseif +:/tst_parser/data/syntaxErrors12.scxml:8:68: error: Unexpected element assign +:/tst_parser/data/syntaxErrors12.scxml:9:63: error: Unexpected element elseif +:/tst_parser/data/syntaxErrors12.scxml:10:68: error: Unexpected element assign +:/tst_parser/data/syntaxErrors12.scxml:11:62: error: Unexpected element elseif +:/tst_parser/data/syntaxErrors12.scxml:12:68: error: Unexpected element assign +:/tst_parser/data/syntaxErrors12.scxml:13:17: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors13.scxml b/tests/auto/parser/data/syntaxErrors13.scxml new file mode 100644 index 0000000..ea0728c --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors13.scxml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + <transition event="OP.INSERT"> + |if cond="_event.data.operator == 'OPER.PLUS'"> + <assign location="long_expr" expr="long_expr+'+'" /> + <elseif cond="_event.data.operator=='OPER.MINUS'" /> + <assign location="long_expr" expr="long_expr+'-'" /> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors13.scxml.errors b/tests/auto/parser/data/syntaxErrors13.scxml.errors new file mode 100644 index 0000000..e3104b2 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors13.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/syntaxErrors13.scxml:7:64: error: Unexpected element elseif +:/tst_parser/data/syntaxErrors13.scxml:9:17: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors14.scxml b/tests/auto/parser/data/syntaxErrors14.scxml new file mode 100644 index 0000000..33890df --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors14.scxml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + <transition event="OP.INSERT"> + <If cond="_event.data.operator == 'OPER.PLUS'"> + <assign location="long_expr" expr="long_expr+'+'" /> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors14.scxml.errors b/tests/auto/parser/data/syntaxErrors14.scxml.errors new file mode 100644 index 0000000..47bf05c --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors14.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/syntaxErrors14.scxml:5:59: error: Unknown element If +:/tst_parser/data/syntaxErrors14.scxml:7:17: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors15.scxml b/tests/auto/parser/data/syntaxErrors15.scxml new file mode 100644 index 0000000..aeeb686 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors15.scxml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" +> + <state id="somewhere"> + <invoke type="http://www.w3.org/TR/scxml/"> + http://www.w3.org/2005/07/scxmlscxml name="anywhere" version="1.0"> + <state id="here"> + </state> + <state id="there"> + </state> + </scxml> + </content> + </invoke> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors15.scxml.errors b/tests/auto/parser/data/syntaxErrors15.scxml.errors new file mode 100644 index 0000000..406e65e --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors15.scxml.errors @@ -0,0 +1,3 @@ +:/tst_parser/data/syntaxErrors15.scxml:9:37: error: Unexpected element state +:/tst_parser/data/syntaxErrors15.scxml:11:38: error: Unexpected element state +:/tst_parser/data/syntaxErrors15.scxml:13:24: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors2.scxml b/tests/auto/parser/data/syntaxErrors2.scxml new file mode 100644 index 0000000..3c97fa7 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors2.scxml @@ -0,0 +1,14 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="lightImpulseGenerator"> + <transition event="scheduleNewImpulse"> + <if cond="In('offState')"> + <send event="lightImpulse" id="lightId" delay="1s"/> + <elseif cond="In('hurryStateOff')"/> + <send>event="lightImpulse" id="lightId" delay="500ms"/> + <else/> + <send event="lightImpulse" id="lightId" delay="200ms"/> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors2.scxml.errors b/tests/auto/parser/data/syntaxErrors2.scxml.errors new file mode 100644 index 0000000..a86e782 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors2.scxml.errors @@ -0,0 +1,3 @@ +:/tst_parser/data/syntaxErrors2.scxml:9:19: error: Unexpected element else +:/tst_parser/data/syntaxErrors2.scxml:10:71: error: Unexpected element send +:/tst_parser/data/syntaxErrors2.scxml:11:17: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors3.scxml b/tests/auto/parser/data/syntaxErrors3.scxml new file mode 100644 index 0000000..e419851 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors3.scxml @@ -0,0 +1,19 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + <state id="ready"> +id="result"> + </state> + </state> + <state id="o]erand1"> + </state> + <state id="operand2"> + <transition event="EQUALS" target="result"> + </transition> + </state> + + <transition event="OP.INSERT"> + <assige location="long_expr" expr="long_expr+'*'" /> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors3.scxml.errors b/tests/auto/parser/data/syntaxErrors3.scxml.errors new file mode 100644 index 0000000..23479f9 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors3.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/syntaxErrors3.scxml:16:64: error: Unknown element assige +:/tst_parser/data/syntaxErrors3.scxml:18:12: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors4.scxml b/tests/auto/parser/data/syntaxErrors4.scxml new file mode 100644 index 0000000..4fb8ab9 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors4.scxml @@ -0,0 +1,17 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper" initial="on"> + <state id="on"> + <state id="negated2"> + <state id="operand2"> + </state> + </state> + <transition event="OP.INSERT"> + <if cond="_event.data.operator == 'OPER.PLUS'"> + <assign location="long_expr" expr="long_expr+'+'" /> + <elsDif cond="_event.data.operator=='OPER.MINUS'" /> + <assign location="long_expr" expr="long_expr+'-'" /> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors4.scxml.errors b/tests/auto/parser/data/syntaxErrors4.scxml.errors new file mode 100644 index 0000000..54e7b4f --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors4.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/syntaxErrors4.scxml:12:64: error: Unknown element elsDif +:/tst_parser/data/syntaxErrors4.scxml:17:8: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors5.scxml b/tests/auto/parser/data/syntaxErrors5.scxml new file mode 100644 index 0000000..d711cbd --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors5.scxml @@ -0,0 +1,13 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="wrapper"> + <transition event="OP.INSERT"> + <if cond="_event.data.operator == 'OPER.PLUS'"> + <assign location="long_expr" expr="long_expr+'+'" /> + <elseif cond="_event.data.operator=='OPER.MINUS'" /> + <state id="zero1"> + tion="long_expr" expr="long_expr+'-'" /> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors5.scxml.errors b/tests/auto/parser/data/syntaxErrors5.scxml.errors new file mode 100644 index 0000000..2e80c0b --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors5.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/syntaxErrors5.scxml:8:20: error: Unexpected element state +:/tst_parser/data/syntaxErrors5.scxml:10:17: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/syntaxErrors7.scxml b/tests/auto/parser/data/syntaxErrors7.scxml new file mode 100644 index 0000000..fbe42ce --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors7.scxml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="lightImpulseGenerator"> + <transition event="scheduleNewImpulse"> + <if cond="In('offState')"> + <send event="lightImpulse"/> + <Elseif cond="In('hurryStateOff')"/> + <send event="lightImpulse"/> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors7.scxml.errors b/tests/auto/parser/data/syntaxErrors7.scxml.errors new file mode 100644 index 0000000..228a80b --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors7.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/syntaxErrors7.scxml:7:48: error: Unknown element Elseif diff --git a/tests/auto/parser/data/syntaxErrors9.scxml b/tests/auto/parser/data/syntaxErrors9.scxml new file mode 100644 index 0000000..5b6ad24 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors9.scxml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"> + <state id="workflow"> + <transition event="updateLights"> + |if cond="In('jackpotStateOn')"> + <raise event="turnOnJackpot"/> + <else/> + <raise event="turnOffJackpot"/> + </if> + </transition> + </state> +</scxml> diff --git a/tests/auto/parser/data/syntaxErrors9.scxml.errors b/tests/auto/parser/data/syntaxErrors9.scxml.errors new file mode 100644 index 0000000..cb5b1e9 --- /dev/null +++ b/tests/auto/parser/data/syntaxErrors9.scxml.errors @@ -0,0 +1,2 @@ +:/tst_parser/data/syntaxErrors9.scxml:7:19: error: Unexpected element else +:/tst_parser/data/syntaxErrors9.scxml:9:17: error: Error parsing SCXML file: Opening and ending tag mismatch. diff --git a/tests/auto/parser/data/test1.scxml.errors b/tests/auto/parser/data/test1.scxml.errors new file mode 100644 index 0000000..bb9d53e --- /dev/null +++ b/tests/auto/parser/data/test1.scxml.errors @@ -0,0 +1 @@ +:/tst_parser/data/test1.scxml:34:46: error: unknown state 'b' in target diff --git a/tests/auto/parser/parser.pro b/tests/auto/parser/parser.pro index 10deb2d..3a21a71 100644 --- a/tests/auto/parser/parser.pro +++ b/tests/auto/parser/parser.pro @@ -11,5 +11,3 @@ RESOURCES += tst_parser.qrc SOURCES += \ tst_parser.cpp - -load(qscxmlc) diff --git a/tests/auto/parser/tst_parser.cpp b/tests/auto/parser/tst_parser.cpp index 4c0e19f..202e783 100644 --- a/tests/auto/parser/tst_parser.cpp +++ b/tests/auto/parser/tst_parser.cpp @@ -29,7 +29,7 @@ #include <QtTest> #include <QObject> #include <QXmlStreamReader> -#include <QtScxml/qscxmlparser.h> +#include <QtScxml/qscxmlcompiler.h> #include <QtScxml/qscxmlstatemachine.h> Q_DECLARE_METATYPE(QScxmlError); @@ -46,107 +46,42 @@ private Q_SLOTS: void tst_Parser::error_data() { QTest::addColumn<QString>("scxmlFileName"); - QTest::addColumn<QVector<QScxmlError> >("expectedErrors"); - - QVector<QScxmlError> errors; - QString filename; - - filename = QLatin1String(":/tst_parser/data/test1.scxml"); - errors.clear(); - errors << QScxmlError(filename, 34, 46, - QLatin1String("unknown state 'b' in target")); - QTest::newRow("test1") << filename << errors; - - filename = QLatin1String(":/tst_parser/data/namespaces1.scxml"); - errors.clear(); - QTest::newRow("namespaces 1") << filename << errors; - - filename = QLatin1String(":/tst_parser/data/ids1.scxml"); - errors.clear(); - QTest::newRow("IDs 1") << filename << errors; - - filename = QLatin1String(":/tst_parser/data/ids2.scxml"); - errors.clear(); - errors << QScxmlError(filename, 33, 25, - QLatin1String("state name 'foo.bar' is not a valid C++ identifier in Qt mode")); - errors << QScxmlError(filename, 34, 25, - QLatin1String("state name 'foo-bar' is not a valid C++ identifier in Qt mode")); - errors << QScxmlError(filename, 36, 19, - QLatin1String("'1' is not a valid XML ID")); - - QTest::newRow("IDs 2") << filename << errors; - - filename = QLatin1String(":/tst_parser/data/eventnames.scxml"); - errors.clear(); - errors << QScxmlError(filename, 50, 38, - QLatin1String("'.invalid' is not a valid event")); - errors << QScxmlError(filename, 51, 38, - QLatin1String("'invalid.' is not a valid event")); - errors << QScxmlError(filename, 39, 36, - QLatin1String("'.invalid' is not a valid event")); - errors << QScxmlError(filename, 40, 36, - QLatin1String("'invalid.' is not a valid event")); - errors << QScxmlError(filename, 41, 36, - QLatin1String("'in valid' is not a valid event")); - QTest::newRow("eventnames") << filename << errors; - - filename = QString(":/tst_parser/data/qtmode.scxml"); - errors.clear(); - errors << QScxmlError(filename, 35, 31, - QLatin1String("event name 'a' collides with a state name 'a' in Qt mode")); - errors << QScxmlError(filename, 36, 34, - QLatin1String("event name 'void' is not a valid C++ identifier in Qt mode")); - errors << QScxmlError(filename, 37, 38, - QLatin1String("event name 'aChanged' collides with a state name 'a' in Qt mode")); - errors << QScxmlError(filename, 38, 38, - QLatin1String("event name 'finished' is not a valid Qt identifier in Qt mode")); - errors << QScxmlError(filename, 42, 21, - QLatin1String("state name 'int' is not a valid C++ identifier in Qt mode")); - errors << QScxmlError(filename, 43, 28, - QLatin1String("state name 'objectName' is not a valid Qt identifier in Qt mode")); - errors << QScxmlError(filename, 45, 28, - QLatin1String("state name 'fooChanged' collides with a state name 'foo' in Qt mode")); - QTest::newRow("qtmode") << filename << errors; - - filename = QString(":/tst_parser/data/scxml1.scxml"); - errors.clear(); - errors << QScxmlError(filename, 32, 36, - QLatin1String("Unsupported data model 'foo' in scxml")); - errors << QScxmlError(filename, 34, 30, - QLatin1String("Unexpected element scxml")); - QTest::newRow("scxml1") << filename << errors; - - filename = QString(":/tst_parser/data/scxml2.scxml"); - errors.clear(); - errors << QScxmlError(filename, 32, 34, - QLatin1String("Unsupperted binding type 'foo'")); - QTest::newRow("scxml2") << filename << errors; + QTest::addColumn<QString>("errorFileName"); + + QDir dir(QLatin1String(":/tst_parser/data/")); + const auto dirEntries = dir.entryList(); + for (const QString &entry : dirEntries) { + if (!entry.endsWith(QLatin1String(".errors"))) { + QString scxmlFileName = dir.filePath(entry); + QTest::newRow(entry.toLatin1().constData()) + << scxmlFileName << (scxmlFileName + QLatin1String(".errors")); + } + } } void tst_Parser::error() { QFETCH(QString, scxmlFileName); - QFETCH(QVector<QScxmlError>, expectedErrors); + QFETCH(QString, errorFileName); + + QFile errorFile(errorFileName); + errorFile.open(QIODevice::ReadOnly | QIODevice::Text); + const QStringList expectedErrors = + QString::fromUtf8(errorFile.readAll()).split('\n', QString::SkipEmptyParts); QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(scxmlFileName)); QVERIFY(!stateMachine.isNull()); - QVector<QScxmlError> errors = stateMachine->parseErrors(); + const QVector<QScxmlError> errors = stateMachine->parseErrors(); if (errors.count() != expectedErrors.count()) { - foreach (const QScxmlError &error, errors) { + for (const QScxmlError &error : errors) { qDebug() << error.toString(); } } QCOMPARE(errors.count(), expectedErrors.count()); - for (int i = 0; i < errors.count(); ++i) { - QScxmlError error = errors.at(i); - QScxmlError expectedError = expectedErrors.at(i); - QCOMPARE(error.fileName(), expectedError.fileName()); - QCOMPARE(error.line(), expectedError.line()); - QCOMPARE(error.column(), expectedError.column()); - QCOMPARE(error.description(), expectedError.description()); - } + for (int i = 0; i < errors.count(); ++i) + QCOMPARE(errors.at(i).toString(), expectedErrors.at(i)); } QTEST_MAIN(tst_Parser) diff --git a/tests/auto/parser/tst_parser.qrc b/tests/auto/parser/tst_parser.qrc index 7323c82..8d133d2 100644 --- a/tests/auto/parser/tst_parser.qrc +++ b/tests/auto/parser/tst_parser.qrc @@ -1,12 +1,84 @@ <RCC> <qresource prefix="/tst_parser"> + <file>data/badInitial.scxml.errors</file> + <file>data/badInitial.scxml</file> + <file>data/commentInScript.scxml.errors</file> + <file>data/commentInScript.scxml</file> + <file>data/empty.scxml.errors</file> + <file>data/empty.scxml</file> + <file>data/eventnames.scxml.errors</file> <file>data/eventnames.scxml</file> + <file>data/ids1.scxml.errors</file> <file>data/ids1.scxml</file> + <file>data/ids2.scxml.errors</file> <file>data/ids2.scxml</file> + <file>data/invalidContent.scxml.errors</file> + <file>data/invalidContent.scxml</file> + <file>data/invalidRoot1.scxml.errors</file> + <file>data/invalidRoot1.scxml</file> + <file>data/invalidRoot2.scxml.errors</file> + <file>data/invalidRoot2.scxml</file> + <file>data/invalidRoot3.scxml.errors</file> + <file>data/invalidRoot3.scxml</file> + <file>data/invalidRoot6.scxml.errors</file> + <file>data/invalidRoot6.scxml</file> + <file>data/invalidstatemachinename.scxml.errors</file> + <file>data/invalidstatemachinename.scxml</file> + <file>data/invalidUnicode1.scxml.errors</file> + <file>data/invalidUnicode1.scxml</file> + <file>data/invalidXmlHeader1.scxml.errors</file> + <file>data/invalidXmlHeader1.scxml</file> + <file>data/invalidXmlHeader2.scxml.errors</file> + <file>data/invalidXmlHeader2.scxml</file> + <file>data/misplacedinvoke.scxml.errors</file> + <file>data/misplacedinvoke.scxml</file> + <file>data/namespaces1.scxml.errors</file> <file>data/namespaces1.scxml</file> - <file>data/qtmode.scxml</file> + <file>data/nestedScxml.scxml.errors</file> + <file>data/nestedScxml.scxml</file> + <file>data/noContentInInvoke1.scxml.errors</file> + <file>data/noContentInInvoke1.scxml</file> + <file>data/noContentInInvoke2.scxml.errors</file> + <file>data/noContentInInvoke2.scxml</file> + <file>data/noContentInInvoke3.scxml.errors</file> + <file>data/noContentInInvoke3.scxml</file> + <file>data/noContentInInvoke4.scxml.errors</file> + <file>data/noContentInInvoke4.scxml</file> + <file>data/prematureEndOfDocument1.scxml.errors</file> + <file>data/prematureEndOfDocument1.scxml</file> + <file>data/prematureEndOfDocument2.scxml.errors</file> + <file>data/prematureEndOfDocument2.scxml</file> + <file>data/scxml1.scxml.errors</file> <file>data/scxml1.scxml</file> + <file>data/scxml2.scxml.errors</file> <file>data/scxml2.scxml</file> + <file>data/syntaxErrors10.scxml.errors</file> + <file>data/syntaxErrors10.scxml</file> + <file>data/syntaxErrors11.scxml.errors</file> + <file>data/syntaxErrors11.scxml</file> + <file>data/syntaxErrors12.scxml.errors</file> + <file>data/syntaxErrors12.scxml</file> + <file>data/syntaxErrors13.scxml.errors</file> + <file>data/syntaxErrors13.scxml</file> + <file>data/syntaxErrors14.scxml.errors</file> + <file>data/syntaxErrors14.scxml</file> + <file>data/syntaxErrors15.scxml.errors</file> + <file>data/syntaxErrors15.scxml</file> + <file>data/syntaxErrors1.scxml.errors</file> + <file>data/syntaxErrors1.scxml</file> + <file>data/syntaxErrors2.scxml.errors</file> + <file>data/syntaxErrors2.scxml</file> + <file>data/syntaxErrors3.scxml.errors</file> + <file>data/syntaxErrors3.scxml</file> + <file>data/syntaxErrors4.scxml.errors</file> + <file>data/syntaxErrors4.scxml</file> + <file>data/syntaxErrors5.scxml.errors</file> + <file>data/syntaxErrors5.scxml</file> + <file>data/syntaxErrors7.scxml.errors</file> + <file>data/syntaxErrors7.scxml</file> + <file>data/syntaxErrors9.scxml.errors</file> + <file>data/syntaxErrors9.scxml</file> + <file>data/test1.scxml.errors</file> <file>data/test1.scxml</file> </qresource> </RCC> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf.scxml b/tests/auto/qscxmlc/data/elseWithoutIf.scxml deleted file mode 100644 index 0674542..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transitifn event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - nameate> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_e:ent.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="shornt.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'." /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - nsi <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assigZ location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf10.scxml b/tests/auto/qscxmlc/data/elseWithoutIf10.scxml deleted file mode 100644 index 920fd84..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf10.scxml +++ /dev/null @@ -1,374 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: no --> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="Pinball" datamodel="ecmascript"> - <datamodel> - <data id="highScore" expr="0"/> - <data id="score" expr="0"/> - </datamodel> - <parallel id="global"> - <parallel id="guiControl"> - <state id="cLight"> - <state id="cLightOn"> - <transition event="turnOffC" target="cLightOff"/> - </state> - <state id="cLightOff"> - <transition event="turnOnC" target="cLightOn"/> - </state> - </state> - <state id="rLight"> - <state id="rLightOn"> - <transition event="turnOffR" target="rLightOff"/> - </state> - <state id="rLightOff"> - <transition event="turnOnR" target="rLightOn"/> - </state> - </state> - <state id="aLight"> - <state id="aLightOn"> - <transition event="turnOffA" target="aLightOff"/> - </state> - <state id="aLightOff"> - <transition event="turnOnA" target="aLightOn"/> - </state> - </state> - <state id="zLight"> - <state id="zLightOn"> - <transition event="turnOffZ" target="zLightOff"/> - </state> - <state id="zLightOff"> - <transition event="turnOnZ" target="zLightOn"/> - </state> - </state> - <state id="yLight"> - <state id="yLightOn"> - <transition event="turnOffY" target="yLightOff"/> - </state> - <state id="yLightOff"> - <transition event="turnOnY" target="yLightOn"/> - </state> - </state> - <state id="hurryLight"> - <state id="hurryLightOn"> - <transition event="turnOffHurry" target="hurryLightOff"/> - </state> - <state id="hurryLightOff"> - <transition event="turnOnHurry" target="hurryLightOn"/> - </state> - </state> - <state id="jackpotLight"> - <state id="jackpotLightOn"> - <transition event="turnOffJackpot" target="jackpotLightOff"/> - </state> - <state id="jackpotLightOff"> - <transition event="turnOnJackpot" target="jackpotLightOn"/> - </state> - </state> - <state id="gameOverLight"> - <state id="gameOverLightOn"> - <transition event="turnOffGameOver" target="gameOverLightOff"/> - </state> - <state id="gameOverLightOff"> - <transition event="turnOnGameOver" target="gameOverLightOn"/> - </state> - </state> - </parallel> - - <parallel id="internalState"> - <parallel id="logicalState"> - <state id="letterState"> - <parallel id="lettersState"> - <state id="letter.C"> - <state id="cLetterOff"> - <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/> - </state> - <final id="cLetterOn"/> - </state> - <state id="letter.R"> - <state id="rLetterOff"> - <transition event="rLetterTriggered" cond="In('onState')" target="rLetterOn"/> - </state> - <final id="rLetterOn"/> - </state> - <state id="letter.A"> - <state id="aLetterOff"> - <transition event="aLetterTriggered" cond="In('onState')" target="aLetterOn"/> - </state> - <final id="aLetterOn"/> - </state> - <state id="letter.Z"> - <state id="zLetterOff"> - <transition event="zLetterTriggered" cond="In('onState')" target="zLetterOn"/> - </state> - <final id="zLetterOn"/> - </state> - <state id="letter.Y"> - <state id="yLetterOff"> - <transition event="yLetterTriggered" cond="In('onState')" target="yLetterOn"/> - </state> - <final id="yLetterOn"/> - </state> - <transition event="resetLetters" target="lettersState"/> - </parallel> - </state> - <state id="modeState"> - <state id="offState"> - <onentry> - <if cond="highScore < score"> - <assign location="highScore" expr="score"/> - </if> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="startTriggered" target="onState"/> - </state> - <parallel id="onState"> - <onentry> - <assign location="score" expr="0"/> - </onentry> - <state id="hurryState"> - <state id="hurryStateOff"> - <onentry> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOn" target="hurryStateOn"/> - </state> - <state id="hurryStateOn"> - <onentry> - <send event="goToHurryOff" id="hurryId" delay="5s"/> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOff" target="hurryStateOff"/> - <onexit> - <cancel sendid="hurryId"/> - </onexit> - </state> - </state> - <state id="jackpotState"> - <state id="jackpotStateOff"> - <onentry> - <raise event="update"/> - </onentry> - <transition event="goForJackpot" target="jackpotStateOn"/> - </state> - <state id="jackpotStateOn"> - <onentry> - <raise event="update"/> - </onentry> - </state> - </state> - <transition event="ballOutTriggered" target="offState"/> - </parallel> - </state> - </parallel> - - <state id="workflow"> - <state id="lightImpulseGenerator"> - <state id="lightImpulseOn"/> - <state id="lightImpulseOff"/> - - <onentry> - <raise event="update"/> - </onentry> - - <transition event="scheduleNewImpulse"> - <cancel sendid="lightId"/> - <if cond="In('offState')"> - <send event="lightImpulse" id="lightId" delay="1s"/> - <elseif cond="In('hurryStateOff')"/> - <send>event="lightImpulse" id="lightId" delay="500ms"/> - <else/> - <send event="lightImpulse" id="lightId" delay="200ms"/> - </if> - </transition> - - <transition event="update"> - <raise event="scheduleNewImpulse"/> - <raise event="updateLights"/> - </transition> - - <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/> - <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/> - </state> - - <transition event="done.state.letter.*"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 1000"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 10000"/> - </if> - <raise event="updateLights"/> - </transition> - - <transition event="done.state.lettersState"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 100000"/> - <raise event="goToHurryOn"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 1000000"/> - <raise event="goToHurryOff"/> - <raise event="goForJackpot"/> - </if> - </transition> - - <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> - <param name="highScore" expr="highScore"/> - <param name="score" expr="score"/> - </send> - <if cond="In('jackpotStateOn')"> - <raise event="turnOnJackpot"/> - <else/> - <raise event="turnOffJackpot"/> - </if> - - <if cond="In('lightImpulseOn')"> - <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOnJackpot"/> - <raise event="turnOnGameOver"/> - <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOffGameOver"/> - </if> - <else/> - <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <raise event="turnOffHurry"/> - <raise event="turnOffJackpot"/> - <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - </if> - </transition> - </state> - </parallel> - </parallel> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf2.scxml b/tests/auto/qscxmlc/data/elseWithoutIf2.scxml deleted file mode 100644 index 830831f..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf2.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="O" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="o]erand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assige location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf3.scxml b/tests/auto/qscxmlc/data/elseWithoutIf3.scxml deleted file mode 100644 index beca8f6..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf3.scxml +++ /dev/null @@ -1,163 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elsDif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf4.scxml b/tests/auto/qscxmlc/data/elseWithoutIf4.scxml deleted file mode 100644 index 8e8018d..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf4.scxml +++ /dev/null @@ -1,165 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <state id="zero1"> - tion="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf5.scxml b/tests/auto/qscxmlc/data/elseWithoutIf5.scxml deleted file mode 100644 index 47af4f3..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf5.scxml +++ /dev/null @@ -1,374 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: no --> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="Pinball" datamodel="ecmascript"> - <datamodel> - <data id="highScore" expr="0"/> - <data id="score" expr="0"/> - </datamodel> - <parallel id="global"> - <parallel id="guiControl"> - <state id="cLight"> - <state id="cLightOn"> - <transition event="turnOffC" target="cLightOff"/> - </state> - <state id="cLightOff"> - <transition event="turnOnC" target="cLightOn"/> - </state> - </state> - <state id="rLight"> - <state id="rLightOn"> - <transition event="turnOffR" target="rLightOff"/> - </state> - <state id="rLightOff"> - <transition event="turnOnR" target="rLightOn"/> - </state> - </state> - <state id="aLight"> - <state id="aLightOn"> - <transition event="turnOffA" target="aLightOff"/> - </state> - <state id="aLightOff"> - <transition event="turnOnA" target="aLightOn"/> - </state> - </state> - <state id="zLight"> - <state id="zLightOn"> - <transition event="turnOffZ" target="zLightOff"/> - </state> - <state id="zLightOff"> - <transition event="turnOnZ" target="zLightOn"/> - </state> - </state> - <state id="yLight"> - <state id="yLightOn"> - <transition event="turnOffY" target="yLightOff"/> - </state> - <state id="yLightOff"> - <transition event="turnOnY" target="yLightOn"/> - </state> - </state> - <state id="hurryLight"> - <state id="hurryLightOn"> - <transition event="turnOffHurry" target="hurryLightOff"/> - </state> - <state id="hurryLightOff"> - <transition event="turnOnHurry" target="hurryLightOn"/> - </state> - </state> - <state id="jackpotLight"> - <state id="jackpotLightOn"> - <transition event="turnOffJackpot" target="jackpotLightOff"/> - </state> - <state id="jackpotLightOff"> - <transition event="turnOnJackpot" target="jackpotLightOn"/> - </state> - </state> - <state id="gameOverLight"> - <state id="gameOverLightOn"> - <transition event="turnOffGameOver" target="gameOverLightOff"/> - </state> - <state id="gameOverLightOff"> - <transition event="turnOnGameOver" target="gameOverLightOn"/> - </state> - </state> - </parallel> - - <parallel id="internalState"> - <parallel id="logicalState"> - <state id="letterState"> - <parallel id="lettersState"> - <state id="letter.C"> - <state id="cLetterOff"> - <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/> - </state> - <final id="cLetterOn"/> - </state> - <state id="letter.R"> - <state id="rLetterOff"> - <transition event="rLetterTriggered" cond="In('onState')" target="rLetterOn"/> - </state> - <final id="rLetterOn"/> - </state> - <state id="letter.A"> - <state id="aLetterOff"> - <transition event="aLetterTriggered" cond="In('onState')" target="aLetterOn"/> - </state> - <final id="aLetterOn"/> - </state> - <state id="letter.Z"> - <state id="zLetterOff"> - <transition event="zLetterTriggered" cond="In('onState')" target="zLetterOn"/> - </state> - <final id="zLetterOn"/> - </state> - <state id="letter.Y"> - <state id="yLetterOff"> - <transition event="yLetterTriggered" cond="In('onState')" target="yLetterOn"/> - </state> - <final id="yLetterOn"/> - </state> - <transition event="resetLetters" target="lettersState"/> - </parallel> - </state> - <state id="modeState"> - <state id="offState"> - <onentry> - <if cond="highScore < score"> - <assign location="highScore" expr="score"/> - </if> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="startTriggered" target="onState"/> - </state> - <parallel id="onState"> - <onentry> - <assign location="score" expr="0"/> - </onentry> - <state id="hurryState"> - <state id="hurryStateOff"> - <onentry> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOn" target="hurryStateOn"/> - </state> - <state id="hurryStateOn"> - <onentry> - <send event="goToHurryOff" id="hurryId" delay="5s"/> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOff" target="hurryStateOff"/> - <onexit> - <cancel sendid="hurryId"/> - </onexit> - </state> - </state> - <state id="jackpotState"> - <state id="jackpotStateOff"> - <onentry> - <raise event="update"/> - </onentry> - <transition event="goForJackpot" target="jackpotStateOn"/> - </state> - <state id="jackpotStateOn"> - <onentry> - <raise event="update"/> - </onentry> - </state> - </state> - <transition event="ballOutTriggered" target="offState"/> - </parallel> - </state> - </parallel> - - <state id="workflow"> - <state id="lightImpulseGenerator"> - <state id="lightImpulseOn"/> - <state id="lightImpulseOff"/> - - <onentry> - <raise event="update"/> - </onentry> - - <transition event="scheduleNewImpulse"> - <cancel sendid="lightId"/> - |if cond="In('offState')"> - <send event="lightImpulse" id="lightId" delay="1s"/> - <elseif cond="In('hurryStateOff')"/> - <send event="lightImpulse" id="lightId" delay="500ms"/> - <else/> - <send event="lightImpulse" id="lightId" delay="200ms"/> - </if> - </transition> - - <transition event="update"> - <raise event="scheduleNewImpulse"/> - <raise event="updateLights"/> - </transition> - - <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/> - <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/> - </state> - - <transition event="done.state.letter.*"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 1000"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 10000"/> - </if> - <raise event="updateLights"/> - </transition> - - <transition event="done.state.lettersState"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 100000"/> - <raise event="goToHurryOn"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 1000000"/> - <raise event="goToHurryOff"/> - <raise event="goForJackpot"/> - </if> - </transition> - - <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> - <param name="highScore" expr="highScore"/> - <param name="score" expr="score"/> - </send> - <if cond="In('jackpotStateOn')"> - <raise event="turnOnJackpot"/> - <else/> - <raise event="turnOffJackpot"/> - </if> - - <if cond="In('lightImpulseOn')"> - <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOnJackpot"/> - <raise event="turnOnGameOver"/> - <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOffGameOver"/> - </if> - <else/> - <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <raise event="turnOffHurry"/> - <raise event="turnOffJackpot"/> - <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - </if> - </transition> - </state> - </parallel> - </parallel> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf6.scxml b/tests/auto/qscxmlc/data/elseWithoutIf6.scxml deleted file mode 100644 index beaf539..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf6.scxml +++ /dev/null @@ -1,374 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: no --> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="Pinball" datamodel="ecmascript"> - <datamodel> - <data id="highScore" expr="0"/> - <data id="score" expr="0"/> - </datamodel> - <parallel id="global"> - <parallel id="guiControl"> - <state id="cLight"> - <state id="cLightOn"> - <transition event="turnOffC" target="cLightOff"/> - </state> - <state id="cLightOff"> - <transition event="turnOnC" target="cLightOn"/> - </state> - </state> - <state id="rLight"> - <state id="rLightOn"> - <transition event="turnOffR" target="rLightOff"/> - </state> - <state id="rLightOff"> - <transition event="turnOnR" target="rLightOn"/> - </state> - </state> - <state id="aLight"> - <state id="aLightOn"> - <transition event="turnOffA" target="aLightOff"/> - </state> - <state id="aLightOff"> - <transition event="turnOnA" target="aLightOn"/> - </state> - </state> - <state id="zLight"> - <state id="zLightOn"> - <transition event="turnOffZ" target="zLightOff"/> - </state> - <state id="zLightOff"> - <transition event="turnOnZ" target="zLightOn"/> - </state> - </state> - <state id="yLight"> - <state id="yLightOn"> - <transition event="turnOffY" target="yLightOff"/> - </state> - <state id="yLightOff"> - <transition event="turnOnY" target="yLightOn"/> - </state> - </state> - <state id="hurryLight"> - <state id="hurryLightOn"> - <transition event="turnOffHurry" target="hurryLightOff"/> - </state> - <state id="hurryLightOff"> - <transition event="turnOnHurry" target="hurryLightOn"/> - </state> - </state> - <state id="jackpotLight"> - <state id="jackpotLightOn"> - <transition event="turnOffJackpot" target="jackpotLightOff"/> - </state> - <state id="jackpotLightOff"> - <transition event="turnOnJackpot" target="jackpotLightOn"/> - </state> - </state> - <state id="gameOverLight"> - <state id="gameOverLightOn"> - <transition event="turnOffGameOver" target="gameOverLightOff"/> - </state> - <state id="gameOverLightOff"> - <transition event="turnOnGameOver" target="gameOverLightOn"/> - </state> - </state> - </parallel> - - <parallel id="internalState"> - <parallel id="logicalState"> - <state id="letterState"> - <parallel id="lettersState"> - <state id="letter.C"> - <state id="cLetterOff"> - <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/> - </state> - <final id="cLetterOn"/> - </state> - <state id="letter.R"> - <state id="rLetterOff"> - <transition event="rLetterTriggered" cond="In('onState')" target="rLetterOn"/> - </state> - <final id="rLetterOn"/> - </state> - <state id="letter.A"> - <state id="aLetterOff"> - <transition event="aLetterTriggered" cond="In('onState')" target="aLetterOn"/> - </state> - <final id="aLetterOn"/> - </state> - <state id="letter.Z"> - <state id="zLetterOff"> - <transition event="zLetterTriggered" cond="In('onState')" target="zLetterOn"/> - </state> - <final id="zLetterOn"/> - </state> - <state id="letter.Y"> - <state id="yLetterOff"> - <transition event="yLetterTriggered" cond="In('onState')" target="yLetterOn"/> - </state> - <final id="yLetterOn"/> - </state> - <transition event="resetLetters" target="lettersState"/> - </parallel> - </state> - <state id="modeState"> - <state id="offState"> - <onentry> - <if cond="highScore < score"> - <assign location="highScore" expr="score"/> - </if> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="startTriggered" target="onState"/> - </state> - <parallel id="onState"> - <onentry> - <assign location="score" expr="0"/> - </onentry> - <state id="hurryState"> - <state id="hurryStateOff"> - <onentry> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOn" target="hurryStateOn"/> - </state> - <state id="hurryStateOn"> - <onentry> - <send event="goToHurryOff" id="hurryId" delay="5s"/> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOff" target="hurryStateOff"/> - <onexit> - <cancel sendid="hurryId"/> - </onexit> - </state> - </state> - <state id="jackpotState"> - <state id="jackpotStateOff"> - <onentry> - <raise event="update"/> - </onentry> - <transition event="goForJackpot" target="jackpotStateOn"/> - </state> - <state id="jackpotStateOn"> - <onentry> - <raise event="update"/> - </onentry> - </state> - </state> - <transition event="ballOutTriggered" target="offState"/> - </parallel> - </state> - </parallel> - - <state id="workflow"> - <state id="lightImpulseGenerator"> - <state id="lightImpulseOn"/> - <state id="lightImpulseOff"/> - - <onentry> - <raise event="update"/> - </onentry> - - <transition event="scheduleNewImpulse"> - <cancel sendid="lightId"/> - <if cond="In('offState')"> - <send event="lightImpulse" id="lightId" delay="1s"/> - <Elseif cond="In('hurryStateOff')"/> - <send event="lightImpulse" id="lightId" delay="500ms"/> - <else/> - <send event="lightImpulse" id="lightId" delay="200ms"/> - </if> - </transition> - - <transition event="update"> - <raise event="scheduleNewImpulse"/> - <raise event="updateLights"/> - </transition> - - <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/> - <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/> - </state> - - <transition event="done.state.letter.*"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 1000"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 10000"/> - </if> - <raise event="updateLights"/> - </transition> - - <transition event="done.state.lettersState"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 100000"/> - <raise event="goToHurryOn"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 1000000"/> - <raise event="goToHurryOff"/> - <raise event="goForJackpot"/> - </if> - </transition> - - <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> - <param name="highScore" expr="highScore"/> - <param name="score" expr="score"/> - </send> - <if cond="In('jackpotStateOn')"> - <raise event="turnOnJackpot"/> - <else/> - <raise event="turnOffJackpot"/> - </if> - - <if cond="In('lightImpulseOn')"> - <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOnJackpot"/> - <raise event="turnOnGameOver"/> - <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOffGameOver"/> - </if> - <else/> - <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <raise event="turnOffHurry"/> - <raise event="turnOffJackpot"/> - <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - </if> - </transition> - </state> - </parallel> - </parallel> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf7.scxml b/tests/auto/qscxmlc/data/elseWithoutIf7.scxml deleted file mode 100644 index 21bfe08..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf7.scxml +++ /dev/null @@ -1,374 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: no --> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="Pinball" datamodel="ecmascript"> - <datamodel> - <data id="highScore" expr="0"/> - <data id="score" expr="0"/> - </datamodel> - <parallel id="global"> - <parallel id="guiControl"> - <state id="cLight"> - <state id="cLightOn"> - <transition event="turnOffC" target="cLightOff"/> - </state> - <state id="cLightOff"> - <transition event="turnOnC" target="cLightOn"/> - </state> - </state> - <state id="rLight"> - <state id="rLightOn"> - <transition event="turnOffR" target="rLightOff"/> - </state> - <state id="rLightOff"> - <transition event="turnOnR" target="rLightOn"/> - </state> - </state> - <state id="aLight"> - <state id="aLightOn"> - <transition event="turnOffA" target="aLightOff"/> - </state> - <state id="aLightOff"> - <transition event="turnOnA" target="aLightOn"/> - </state> - </state> - <state id="zLight"> - <state id="zLightOn"> - <transition event="turnOffZ" target="zLightOff"/> - </state> - <state id="zLightOff"> - <transition event="turnOnZ" target="zLightOn"/> - </state> - </state> - <state id="yLight"> - <state id="yLightOn"> - <transition event="turnOffY" target="yLightOff"/> - </state> - <state id="yLightOff"> - <transition event="turnOnY" target="yLightOn"/> - </state> - </state> - <state id="hurryLight"> - <state id="hurryLightOn"> - <transition event="turnOffHurry" target="hurryLightOff"/> - </state> - <state id="hurryLightOff"> - <transition event="turnOnHurry" target="hurryLightOn"/> - </state> - </state> - <state id="jackpotLight"> - <state id="jackpotLightOn"> - <transition event="turnOffJackpot" target="jackpotLightOff"/> - </state> - <state id="jackpotLightOff"> - <transition event="turnOnJackpot" target="jackpotLightOn"/> - </state> - </state> - <state id="gameOverLight"> - <state id="gameOverLightOn"> - <transition event="turnOffGameOver" target="gameOverLightOff"/> - </state> - <state id="gameOverLightOff"> - <transition event="turnOnGameOver" target="gameOverLightOn"/> - </state> - </state> - </parallel> - - <parallel id="internalState"> - <parallel id="logicalState"> - <state id="letterState"> - <parallel id="lettersState"> - <state id="letter.C"> - <state id="cLetterOff"> - <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/> - </state> - <final id="cLetterOn"/> - </state> - <state id="letter.R"> - <state id="rLetterOff"> - <transition event="rLetterTriggered" cond="In('onState')" target="rLetterOn"/> - </state> - <final id="rLetterOn"/> - </state> - <state id="letter.A"> - <state id="aLetterOff"> - <transition event="aLetterTriggered" cond="In('onState')" target="aLetterOn"/> - </state> - <final id="aLetterOn"/> - </state> - <state id="letter.Z"> - <state id="zLetterOff"> - <transition event="zLetterTriggered" cond="In('onState')" target="zLetterOn"/> - </state> - <final id="zLetterOn"/> - </state> - <state id="letter.Y"> - <state id="yLetterOff"> - <transition event="yLetterTriggered" cond="In('onState')" target="yLetterOn"/> - </state> - <final id="yLetterOn"/> - </state> - <transition event="resetLetters" target="lettersState"/> - </parallel> - </state> - <state id="modeState"> - <state id="offState"> - <onentry> - <if cond="highScore < score"> - <assign location="highScore" expr="score"/> - </if> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="startTriggered" target="onState"/> - </state> - <parallel id="onState"> - <onentry> - <assign location="score" expr="0"/> - </onentry> - <state id="hurryState"> - <state id="hurryStateOff"> - <onentry> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOn" target="hurryStateOn"/> - </state> - <state id="hurryStateOn"> - <onentry> - <send event="goToHurryOff" id="hurryId" delay="5s"/> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOff" target="hurryStateOff"/> - <onexit> - <cancel sendid="hurryId"/> - </onexit> - </state> - </state> - <state id="jackpotState"> - <state id="jackpotStateOff"> - <onentry> - <raise event="update"/> - </onentry> - <transition event="goForJackpot" target="jackpotStateOn"/> - </state> - <state id="jackpotStateOn"> - <onentry> - <raise event="update"/> - </onentry> - </state> - </state> - <transition event="ballOutTriggered" target="offState"/> - </parallel> - </state> - </parallel> - - <state id="workflow"> - <state id="lightImpulseGenerator"> - <state id="lightImpulseOn"/> - <state id="lightImpulseOff"/> - - <onentry> - <raise event="update"/> - </onentry> - - <transition event="scheduleNewImpulse"> - <cancel sendid="lightId"/> - <if cond="In('offState')"> - <send event="lightImpulse" id="lightId" delay="1s"/> - <elseif cond="In('hurryStateOff')"/> - <send event="lightImpulse" id="lightId" delay="500ms"/> - <else/> - <send event="lightImpulse" id="lightId" delay="200ms"/> - </if> - </transition> - - <transition event="update"> - <raise event="scheduleNewImpulse"/> - <raise event="updateLights"/> - </transition> - - <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/> - <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/> - </state> - - <transition event="done.state.letter.*"> - |if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 1000"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 10000"/> - </if> - <raise event="updateLights"/> - </transition> - - <transition event="done.state.lettersState"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 100000"/> - <raise event="goToHurryOn"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 1000000"/> - <raise event="goToHurryOff"/> - <raise event="goForJackpot"/> - </if> - </transition> - - <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> - <param name="highScore" expr="highScore"/> - <param name="score" expr="score"/> - </send> - <if cond="In('jackpotStateOn')"> - <raise event="turnOnJackpot"/> - <else/> - <raise event="turnOffJackpot"/> - </if> - - <if cond="In('lightImpulseOn')"> - <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOnJackpot"/> - <raise event="turnOnGameOver"/> - <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOffGameOver"/> - </if> - <else/> - <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <raise event="turnOffHurry"/> - <raise event="turnOffJackpot"/> - <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - </if> - </transition> - </state> - </parallel> - </parallel> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf8.scxml b/tests/auto/qscxmlc/data/elseWithoutIf8.scxml deleted file mode 100644 index b49bd32..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf8.scxml +++ /dev/null @@ -1,374 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: no --> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="Pinball" datamodel="ecmascript"> - <datamodel> - <data id="highScore" expr="0"/> - <data id="score" expr="0"/> - </datamodel> - <parallel id="global"> - <parallel id="guiControl"> - <state id="cLight"> - <state id="cLightOn"> - <transition event="turnOffC" target="cLightOff"/> - </state> - <state id="cLightOff"> - <transition event="turnOnC" target="cLightOn"/> - </state> - </state> - <state id="rLight"> - <state id="rLightOn"> - <transition event="turnOffR" target="rLightOff"/> - </state> - <state id="rLightOff"> - <transition event="turnOnR" target="rLightOn"/> - </state> - </state> - <state id="aLight"> - <state id="aLightOn"> - <transition event="turnOffA" target="aLightOff"/> - </state> - <state id="aLightOff"> - <transition event="turnOnA" target="aLightOn"/> - </state> - </state> - <state id="zLight"> - <state id="zLightOn"> - <transition event="turnOffZ" target="zLightOff"/> - </state> - <state id="zLightOff"> - <transition event="turnOnZ" target="zLightOn"/> - </state> - </state> - <state id="yLight"> - <state id="yLightOn"> - <transition event="turnOffY" target="yLightOff"/> - </state> - <state id="yLightOff"> - <transition event="turnOnY" target="yLightOn"/> - </state> - </state> - <state id="hurryLight"> - <state id="hurryLightOn"> - <transition event="turnOffHurry" target="hurryLightOff"/> - </state> - <state id="hurryLightOff"> - <transition event="turnOnHurry" target="hurryLightOn"/> - </state> - </state> - <state id="jackpotLight"> - <state id="jackpotLightOn"> - <transition event="turnOffJackpot" target="jackpotLightOff"/> - </state> - <state id="jackpotLightOff"> - <transition event="turnOnJackpot" target="jackpotLightOn"/> - </state> - </state> - <state id="gameOverLight"> - <state id="gameOverLightOn"> - <transition event="turnOffGameOver" target="gameOverLightOff"/> - </state> - <state id="gameOverLightOff"> - <transition event="turnOnGameOver" target="gameOverLightOn"/> - </state> - </state> - </parallel> - - <parallel id="internalState"> - <parallel id="logicalState"> - <state id="letterState"> - <parallel id="lettersState"> - <state id="letter.C"> - <state id="cLetterOff"> - <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/> - </state> - <final id="cLetterOn"/> - </state> - <state id="letter.R"> - <state id="rLetterOff"> - <transition event="rLetterTriggered" cond="In('onState')" target="rLetterOn"/> - </state> - <final id="rLetterOn"/> - </state> - <state id="letter.A"> - <state id="aLetterOff"> - <transition event="aLetterTriggered" cond="In('onState')" target="aLetterOn"/> - </state> - <final id="aLetterOn"/> - </state> - <state id="letter.Z"> - <state id="zLetterOff"> - <transition event="zLetterTriggered" cond="In('onState')" target="zLetterOn"/> - </state> - <final id="zLetterOn"/> - </state> - <state id="letter.Y"> - <state id="yLetterOff"> - <transition event="yLetterTriggered" cond="In('onState')" target="yLetterOn"/> - </state> - <final id="yLetterOn"/> - </state> - <transition event="resetLetters" target="lettersState"/> - </parallel> - </state> - <state id="modeState"> - <state id="offState"> - <onentry> - <if cond="highScore < score"> - <assign location="highScore" expr="score"/> - </if> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="startTriggered" target="onState"/> - </state> - <parallel id="onState"> - <onentry> - <assign location="score" expr="0"/> - </onentry> - <state id="hurryState"> - <state id="hurryStateOff"> - <onentry> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOn" target="hurryStateOn"/> - </state> - <state id="hurryStateOn"> - <onentry> - <send event="goToHurryOff" id="hurryId" delay="5s"/> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOff" target="hurryStateOff"/> - <onexit> - <cancel sendid="hurryId"/> - </onexit> - </state> - </state> - <state id="jackpotState"> - <state id="jackpotStateOff"> - <onentry> - <raise event="update"/> - </onentry> - <transition event="goForJackpot" target="jackpotStateOn"/> - </state> - <state id="jackpotStateOn"> - <onentry> - <raise event="update"/> - </onentry> - </state> - </state> - <transition event="ballOutTriggered" target="offState"/> - </parallel> - </state> - </parallel> - - <state id="workflow"> - <state id="lightImpulseGenerator"> - <state id="lightImpulseOn"/> - <state id="lightImpulseOff"/> - - <onentry> - <raise event="update"/> - </onentry> - - <transition event="scheduleNewImpulse"> - <cancel sendid="lightId"/> - <if cond="In('offState')"> - <send event="lightImpulse" id="lightId" delay="1s"/> - <elseif cond="In('hurryStateOff')"/> - <send event="lightImpulse" id="lightId" delay="500ms"/> - <else/> - <send event="lightImpulse" id="lightId" delay="200ms"/> - </if> - </transition> - - <transition event="update"> - <raise event="scheduleNewImpulse"/> - <raise event="updateLights"/> - </transition> - - <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/> - <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/> - </state> - - <transition event="done.state.letter.*"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 1000"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 10000"/> - </if> - <raise event="updateLights"/> - </transition> - - <transition event="done.state.lettersState"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 100000"/> - <raise event="goToHurryOn"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 1000000"/> - <raise event="goToHurryOff"/> - <raise event="goForJackpot"/> - </if> - </transition> - - <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> - <param name="highScore" expr="highScore"/> - <param name="score" expr="score"/> - </send> - |if cond="In('jackpotStateOn')"> - <raise event="turnOnJackpot"/> - <else/> - <raise event="turnOffJackpot"/> - </if> - - <if cond="In('lightImpulseOn')"> - <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOnJackpot"/> - <raise event="turnOnGameOver"/> - <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOffGameOver"/> - </if> - <else/> - <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <raise event="turnOffHurry"/> - <raise event="turnOffJackpot"/> - <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - </if> - </transition> - </state> - </parallel> - </parallel> -</scxml> diff --git a/tests/auto/qscxmlc/data/elseWithoutIf9.scxml b/tests/auto/qscxmlc/data/elseWithoutIf9.scxml deleted file mode 100644 index b753246..0000000 --- a/tests/auto/qscxmlc/data/elseWithoutIf9.scxml +++ /dev/null @@ -1,374 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: no --> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="Pinball" datamodel="ecmascript"> - <datamodel> - <data id="highScore" expr="0"/> - <data id="score" expr="0"/> - </datamodel> - <parallel id="global"> - <parallel id="guiControl"> - <state id="cLight"> - <state id="cLightOn"> - <transition event="turnOffC" target="cLightOff"/> - </state> - <state id="cLightOff"> - <transition event="turnOnC" target="cLightOn"/> - </state> - </state> - <state id="rLight"> - <state id="rLightOn"> - <transition event="turnOffR" target="rLightOff"/> - </state> - <state id="rLightOff"> - <transition event="turnOnR" target="rLightOn"/> - </state> - </state> - <state id="aLight"> - <state id="aLightOn"> - <transition event="turnOffA" target="aLightOff"/> - </state> - <state id="aLightOff"> - <transition event="turnOnA" target="aLightOn"/> - </state> - </state> - <state id="zLight"> - <state id="zLightOn"> - <transition event="turnOffZ" target="zLightOff"/> - </state> - <state id="zLightOff"> - <transition event="turnOnZ" target="zLightOn"/> - </state> - </state> - <state id="yLight"> - <state id="yLightOn"> - <transition event="turnOffY" target="yLightOff"/> - </state> - <state id="yLightOff"> - <transition event="turnOnY" target="yLightOn"/> - </state> - </state> - <state id="hurryLight"> - <state id="hurryLightOn"> - <transition event="turnOffHurry" target="hurryLightOff"/> - </state> - <state id="hurryLightOff"> - <transition event="turnOnHurry" target="hurryLightOn"/> - </state> - </state> - <state id="jackpotLight"> - <state id="jackpotLightOn"> - <transition event="turnOffJackpot" target="jackpotLightOff"/> - </state> - <state id="jackpotLightOff"> - <transition event="turnOnJackpot" target="jackpotLightOn"/> - </state> - </state> - <state id="gameOverLight"> - <state id="gameOverLightOn"> - <transition event="turnOffGameOver" target="gameOverLightOff"/> - </state> - <state id="gameOverLightOff"> - <transition event="turnOnGameOver" target="gameOverLightOn"/> - </state> - </state> - </parallel> - - <parallel id="internalState"> - <parallel id="logicalState"> - <state id="letterState"> - <parallel id="lettersState"> - <state id="letter.C"> - <state id="cLetterOff"> - <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/> - </state> - <final id="cLetterOn"/> - </state> - <state id="letter.R"> - <state id="rLetterOff"> - <transition event="rLetterTriggered" cond="In('onState')" target="rLetterOn"/> - </state> - <final id="rLetterOn"/> - </state> - <state id="letter.A"> - <state id="aLetterOff"> - <transition event="aLetterTriggered" cond="In('onState')" target="aLetterOn"/> - </state> - <final id="aLetterOn"/> - </state> - <state id="letter.Z"> - <state id="zLetterOff"> - <transition event="zLetterTriggered" cond="In('onState')" target="zLetterOn"/> - </state> - <final id="zLetterOn"/> - </state> - <state id="letter.Y"> - <state id="yLetterOff"> - <transition event="yLetterTriggered" cond="In('onState')" target="yLetterOn"/> - </state> - <final id="yLetterOn"/> - </state> - <transition event="resetLetters" target="lettersState"/> - </parallel> - </state> - <state id="modeState"> - <state id="offState"> - <onentry> - <if cond="highScore < score"> - <assign location="highScore" expr="score"/> - </if> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="startTriggered" target="onState"/> - </state> - <parallel id="onState"> - <onentry> - <assign location="score" expr="0"/> - </onentry> - <state id="hurryState"> - <state id="hurryStateOff"> - <onentry> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOn" target="hurryStateOn"/> - </state> - <state id="hurryStateOn"> - <onentry> - <send event="goToHurryOff" id="hurryId" delay="5s"/> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOff" target="hurryStateOff"/> - <onexit> - <cancel sendid="hurryId"/> - </onexit> - </state> - </state> - <state id="jackpotState"> - <state id="jackpotStateOff"> - <onentry> - <raise event="update"/> - </onentry> - <transition event="goForJackpot" target="jackpotStateOn"/> - </state> - <state id="jackpotStateOn"> - <onentry> - <raise event="update"/> - </onentry> - </state> - </state> - <transition event="ballOutTriggered" target="offState"/> - </parallel> - </state> - </parallel> - - <state id="workflow"> - <state id="lightImpulseGenerator"> - <state id="lightImpulseOn"/> - <state id="lightImpulseOff"/> - - <onentry> - <raise event="update"/> - </onentry> - - <transition event="scheduleNewImpulse"> - <cancel sendid="lightId"/> - <if cond="In('offState')"> - <send event="lightImpulse" id="lightId" delay="1s"/> - <elseif cond="In('hurryStateOff')"/> - <send event="lightImpulse" id="lightId" delay="500ms"/> - <else/> - <send event="lightImpulse" id="lightId" delay="200ms"/> - </if> - </transition> - - <transition event="update"> - <raise event="scheduleNewImpulse"/> - <raise event="updateLights"/> - </transition> - - <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/> - <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/> - </state> - - <transition event="done.state.letter.*"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 1000"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 10000"/> - </if> - <raise event="updateLights"/> - </transition> - - <transition event="done.state.lettersState"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 100000"/> - <raise event="goToHurryOn"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 1000000"/> - <raise event="goToHurryOff"/> - <raise event="goForJackpot"/> - </if> - </transition> - - <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> - <param name="highScore" expr="highScore"/> - <param name="score" expr="score"/> - </send> - <if cond="In('jackpotStateOn')"> - <raise event="turnOnJackpot"/> - <else/> - <raise event="turnOffJackpot"/> - </if> - - |if cond="In('lightImpulseOn')"> - <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOnJackpot"/> - <raise event="turnOnGameOver"/> - <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOffGameOver"/> - </if> - <else/> - <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <raise event="turnOffHurry"/> - <raise event="turnOffJackpot"/> - <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - </if> - </transition> - </state> - </parallel> - </parallel> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidAssign.scxml b/tests/auto/qscxmlc/data/invalidAssign.scxml deleted file mode 100644 index 3e22993..0000000 --- a/tests/auto/qscxmlc/data/invalidAssign.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <Assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidAssign2.scxml b/tests/auto/qscxmlc/data/invalidAssign2.scxml deleted file mode 100644 index fb50085..0000000 --- a/tests/auto/qscxmlc/data/invalidAssign2.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr">expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidIf.scxml b/tests/auto/qscxmlc/data/invalidIf.scxml deleted file mode 100644 index 16788fb..0000000 --- a/tests/auto/qscxmlc/data/invalidIf.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - |if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidIf2.scxml b/tests/auto/qscxmlc/data/invalidIf2.scxml deleted file mode 100644 index 7239721..0000000 --- a/tests/auto/qscxmlc/data/invalidIf2.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <If cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidInvoke4.scxml b/tests/auto/qscxmlc/data/invalidInvoke4.scxml deleted file mode 100644 index 5743c85..0000000 --- a/tests/auto/qscxmlc/data/invalidInvoke4.scxml +++ /dev/null @@ -1,79 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - http://www.w3.org/2005/07/scxmlscxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidRoot.scxml b/tests/auto/qscxmlc/data/invalidRoot.scxml deleted file mode 100644 index 8f23220..0000000 --- a/tests/auto/qscxmlc/data/invalidRoot.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" ?> -<Scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidRoot3.scxml b/tests/auto/qscxmlc/data/invalidRoot3.scxml Binary files differdeleted file mode 100644 index f0f4a79..0000000 --- a/tests/auto/qscxmlc/data/invalidRoot3.scxml +++ /dev/null diff --git a/tests/auto/qscxmlc/data/invalidRoot4.scxml b/tests/auto/qscxmlc/data/invalidRoot4.scxml deleted file mode 100644 index 2e2b6f0..0000000 --- a/tests/auto/qscxmlc/data/invalidRoot4.scxml +++ /dev/null @@ -1,374 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: no --> -<Scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - name="Pinball" datamodel="ecmascript"> - <datamodel> - <data id="highScore" expr="0"/> - <data id="score" expr="0"/> - </datamodel> - <parallel id="global"> - <parallel id="guiControl"> - <state id="cLight"> - <state id="cLightOn"> - <transition event="turnOffC" target="cLightOff"/> - </state> - <state id="cLightOff"> - <transition event="turnOnC" target="cLightOn"/> - </state> - </state> - <state id="rLight"> - <state id="rLightOn"> - <transition event="turnOffR" target="rLightOff"/> - </state> - <state id="rLightOff"> - <transition event="turnOnR" target="rLightOn"/> - </state> - </state> - <state id="aLight"> - <state id="aLightOn"> - <transition event="turnOffA" target="aLightOff"/> - </state> - <state id="aLightOff"> - <transition event="turnOnA" target="aLightOn"/> - </state> - </state> - <state id="zLight"> - <state id="zLightOn"> - <transition event="turnOffZ" target="zLightOff"/> - </state> - <state id="zLightOff"> - <transition event="turnOnZ" target="zLightOn"/> - </state> - </state> - <state id="yLight"> - <state id="yLightOn"> - <transition event="turnOffY" target="yLightOff"/> - </state> - <state id="yLightOff"> - <transition event="turnOnY" target="yLightOn"/> - </state> - </state> - <state id="hurryLight"> - <state id="hurryLightOn"> - <transition event="turnOffHurry" target="hurryLightOff"/> - </state> - <state id="hurryLightOff"> - <transition event="turnOnHurry" target="hurryLightOn"/> - </state> - </state> - <state id="jackpotLight"> - <state id="jackpotLightOn"> - <transition event="turnOffJackpot" target="jackpotLightOff"/> - </state> - <state id="jackpotLightOff"> - <transition event="turnOnJackpot" target="jackpotLightOn"/> - </state> - </state> - <state id="gameOverLight"> - <state id="gameOverLightOn"> - <transition event="turnOffGameOver" target="gameOverLightOff"/> - </state> - <state id="gameOverLightOff"> - <transition event="turnOnGameOver" target="gameOverLightOn"/> - </state> - </state> - </parallel> - - <parallel id="internalState"> - <parallel id="logicalState"> - <state id="letterState"> - <parallel id="lettersState"> - <state id="letter.C"> - <state id="cLetterOff"> - <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/> - </state> - <final id="cLetterOn"/> - </state> - <state id="letter.R"> - <state id="rLetterOff"> - <transition event="rLetterTriggered" cond="In('onState')" target="rLetterOn"/> - </state> - <final id="rLetterOn"/> - </state> - <state id="letter.A"> - <state id="aLetterOff"> - <transition event="aLetterTriggered" cond="In('onState')" target="aLetterOn"/> - </state> - <final id="aLetterOn"/> - </state> - <state id="letter.Z"> - <state id="zLetterOff"> - <transition event="zLetterTriggered" cond="In('onState')" target="zLetterOn"/> - </state> - <final id="zLetterOn"/> - </state> - <state id="letter.Y"> - <state id="yLetterOff"> - <transition event="yLetterTriggered" cond="In('onState')" target="yLetterOn"/> - </state> - <final id="yLetterOn"/> - </state> - <transition event="resetLetters" target="lettersState"/> - </parallel> - </state> - <state id="modeState"> - <state id="offState"> - <onentry> - <if cond="highScore < score"> - <assign location="highScore" expr="score"/> - </if> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="startTriggered" target="onState"/> - </state> - <parallel id="onState"> - <onentry> - <assign location="score" expr="0"/> - </onentry> - <state id="hurryState"> - <state id="hurryStateOff"> - <onentry> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOn" target="hurryStateOn"/> - </state> - <state id="hurryStateOn"> - <onentry> - <send event="goToHurryOff" id="hurryId" delay="5s"/> - <raise event="resetLetters"/> - <raise event="update"/> - </onentry> - <transition event="goToHurryOff" target="hurryStateOff"/> - <onexit> - <cancel sendid="hurryId"/> - </onexit> - </state> - </state> - <state id="jackpotState"> - <state id="jackpotStateOff"> - <onentry> - <raise event="update"/> - </onentry> - <transition event="goForJackpot" target="jackpotStateOn"/> - </state> - <state id="jackpotStateOn"> - <onentry> - <raise event="update"/> - </onentry> - </state> - </state> - <transition event="ballOutTriggered" target="offState"/> - </parallel> - </state> - </parallel> - - <state id="workflow"> - <state id="lightImpulseGenerator"> - <state id="lightImpulseOn"/> - <state id="lightImpulseOff"/> - - <onentry> - <raise event="update"/> - </onentry> - - <transition event="scheduleNewImpulse"> - <cancel sendid="lightId"/> - <if cond="In('offState')"> - <send event="lightImpulse" id="lightId" delay="1s"/> - <elseif cond="In('hurryStateOff')"/> - <send event="lightImpulse" id="lightId" delay="500ms"/> - <else/> - <send event="lightImpulse" id="lightId" delay="200ms"/> - </if> - </transition> - - <transition event="update"> - <raise event="scheduleNewImpulse"/> - <raise event="updateLights"/> - </transition> - - <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/> - <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/> - </state> - - <transition event="done.state.letter.*"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 1000"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 10000"/> - </if> - <raise event="updateLights"/> - </transition> - - <transition event="done.state.lettersState"> - <if cond="In('hurryStateOff')"> - <assign location="score" expr="score + 100000"/> - <raise event="goToHurryOn"/> - <elseif cond="In('hurryStateOn')"/> - <assign location="score" expr="score + 1000000"/> - <raise event="goToHurryOff"/> - <raise event="goForJackpot"/> - </if> - </transition> - - <transition event="updateLights"> - <send type="qt:signal" event="updateScore"> - <param name="highScore" expr="highScore"/> - <param name="score" expr="score"/> - </send> - <if cond="In('jackpotStateOn')"> - <raise event="turnOnJackpot"/> - <else/> - <raise event="turnOffJackpot"/> - </if> - - <if cond="In('lightImpulseOn')"> - <if cond="In('offState')"> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOnJackpot"/> - <raise event="turnOnGameOver"/> - <elseif cond="In('hurryStateOff')"/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - <else/> - <raise event="turnOnC"/> - <raise event="turnOnR"/> - <raise event="turnOnA"/> - <raise event="turnOnZ"/> - <raise event="turnOnY"/> - <raise event="turnOnHurry"/> - <raise event="turnOffGameOver"/> - </if> - <else/> - <if cond="In('offState')"> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <raise event="turnOffHurry"/> - <raise event="turnOffJackpot"/> - <elseif cond="In('hurryStateOff')"/> - <raise event="turnOffC"/> - <raise event="turnOffR"/> - <raise event="turnOffA"/> - <raise event="turnOffZ"/> - <raise event="turnOffY"/> - <else/> - <if cond="In('cLetterOn')"> - <raise event="turnOnC"/> - <else/> - <raise event="turnOffC"/> - </if> - <if cond="In('rLetterOn')"> - <raise event="turnOnR"/> - <else/> - <raise event="turnOffR"/> - </if> - <if cond="In('aLetterOn')"> - <raise event="turnOnA"/> - <else/> - <raise event="turnOffA"/> - </if> - <if cond="In('zLetterOn')"> - <raise event="turnOnZ"/> - <else/> - <raise event="turnOffZ"/> - </if> - <if cond="In('yLetterOn')"> - <raise event="turnOnY"/> - <else/> - <raise event="turnOffY"/> - </if> - </if> - <raise event="turnOffHurry"/> - <raise event="turnOffGameOver"/> - </if> - </transition> - </state> - </parallel> - </parallel> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidScxml.scxml b/tests/auto/qscxmlc/data/invalidScxml.scxml deleted file mode 100644 index 9c3ba01..0000000 --- a/tests/auto/qscxmlc/data/invalidScxml.scxml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<Scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <content> - <scxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/invalidXmlHeader.scxml b/tests/auto/qscxmlc/data/invalidXmlHeader.scxml Binary files differdeleted file mode 100644 index 84a6c35..0000000 --- a/tests/auto/qscxmlc/data/invalidXmlHeader.scxml +++ /dev/null diff --git a/tests/auto/qscxmlc/data/invalidXmlHeader2.scxml b/tests/auto/qscxmlc/data/invalidXmlHeader2.scxml deleted file mode 100644 index d2b0a3b..0000000 --- a/tests/auto/qscxmlc/data/invalidXmlHeader2.scxml +++ /dev/null @@ -1,164 +0,0 @@ -<ry>l version="1.0" ?> -<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" - initial="wrapper" datamodel="ecmascript" name="CalculatorStateMachine"> - <datamodel> - <data id="long_expr" /> - <data id="short_expr" /> - <data id="res" /> - </datamodel> - <state id="wrapper" initial="on"> - <state id="on" initial="ready"> - <onentry> - <send event="DISPLAY.UPDATE" /> - </onentry> - <state id="ready" initial="begin"> - <state id="begin"> - <transition event="OPER.MINUS" target="negated1" /> - <onentry> - <assign location="long_expr" expr="''" /> - <assign location="short_expr" expr="0" /> - <assign location="res" expr="0" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="result"> - </state> - <transition event="OPER" target="opEntered" /> - <transition event="DIGIT.0" target="zero1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="DIGIT" target="int1"> - <assign location="short_expr" expr="''" /> - </transition> - <transition event="POINT" target="frac1"> - <assign location="short_expr" expr="''" /> - </transition> - </state> - <state id="negated1"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero1" /> - <transition event="DIGIT" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="operand1"> - <state id="zero1"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> - <transition event="POINT" target="frac1" /> - </state> - <state id="int1"> - <transition event="POINT" target="frac1" /> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - </state> - <state id="frac1"> - <onentry> - <assign location="short_expr" expr="short_expr+'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered" /> - </state> - <state id="opEntered"> - <transition event="OPER.MINUS" target="negated2" /> - <transition event="POINT" target="frac2" /> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <onentry> - <raise event="CALC.SUB" /> - <send target="#_internal" event="OP.INSERT"> - <param name="operator" expr="_event.name" /> - </send> - </onentry> - </state> - <state id="negated2"> - <onentry> - <assign location="short_expr" expr="'-'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT.0" target="zero2" /> - <transition event="DIGIT" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="operand2"> - <state id="zero2"> - <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> - <transition event="POINT" target="frac2" /> - </state> - <state id="int2"> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <onentry> - <assign location="short_expr" expr="short_expr+_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="POINT" target="frac2" /> - </state> - <state id="frac2"> - <onentry> - <assign location="short_expr" expr="short_expr +'.'" /> - <send event="DISPLAY.UPDATE" /> - </onentry> - <transition event="DIGIT"> - <assign location="short_expr" expr="short_expr +_event.name.substr(_event.name.lastIndexOf('.')+1)" /> - <send event="DISPLAY.UPDATE" /> - </transition> - </state> - <transition event="OPER" target="opEntered"> - <raise event="CALC.SUB" /> - <raise event="OP.INSERT" /> - </transition> - <transition event="EQUALS" target="result"> - <raise event="CALC.SUB" /> - <raise event="CALC.DO" /> - </transition> - </state> - <transition event="C" target="on" /> - </state> - <transition event="CALC.DO"> - <assign location="short_expr" expr="''+ res" /> - <assign location="long_expr" expr="''" /> - <assign location="res" expr="0" /> - </transition> - <transition event="CALC.SUB"> - <if cond="short_expr!=''"> - <assign location="long_expr" expr="long_expr+'('+short_expr+')'" /> - </if> - <assign location="res" expr="eval(long_expr)" /> - <assign location="short_expr" expr="''" /> - <send event="DISPLAY.UPDATE" /> - </transition> - <transition event="DISPLAY.UPDATE"> - <log label="'result'" expr="short_expr==''?res:short_expr" /> - <send type="qt:signal" event="updateDisplay"> - <param name="display" expr="short_expr==''?res:short_expr"/> - </send> - </transition> - <transition event="OP.INSERT"> - <log expr="_event.data.operator" /> - <if cond="_event.data.operator == 'OPER.PLUS'"> - <assign location="long_expr" expr="long_expr+'+'" /> - <elseif cond="_event.data.operator=='OPER.MINUS'" /> - <assign location="long_expr" expr="long_expr+'-'" /> - <elseif cond="_event.data.operator=='OPER.STAR'" /> - <assign location="long_expr" expr="long_expr+'*'" /> - <elseif cond="_event.data.operator=='OPER.DIV'" /> - <assign location="long_expr" expr="long_expr+'/'" /> - </if> - </transition> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/noContentInInvoke4.scxml b/tests/auto/qscxmlc/data/noContentInInvoke4.scxml deleted file mode 100644 index 49eaa54..0000000 --- a/tests/auto/qscxmlc/data/noContentInInvoke4.scxml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplacE" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - - <state id="nowhere"/> - <state id="s* LIMITomewhere"> - <invoke type="http://www.w$.org/TR/scxml/"> - <content> - <scxml name="anywhere" version="1.0"> - <sta e id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxm8> - </content> - </invoke> - </state> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/prematureEndOfDocument.scxml b/tests/auto/qscxmlc/data/prematureEndOfDocument.scxml deleted file mode 100644 index 73e0138..0000000 --- a/tests/auto/qscxmlc/data/prematureEndOfDocument.scxml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: yes m-> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <content> - <scxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/prematureEndOfDocument2.scxml b/tests/auto/qscxmlc/data/prematureEndOfDocument2.scxml deleted file mode 100644 index ce56def..0000000 --- a/tests/auto/qscxmlc/data/prematureEndOfDocument2.scxml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <content> -<!-- <scxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/data/wrongRoot.scxml b/tests/auto/qscxmlc/data/wrongRoot.scxml deleted file mode 100644 index ce225b0..0000000 --- a/tests/auto/qscxmlc/data/wrongRoot.scxml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/**************************************************************************** -** -** 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:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ ---> -<!-- enable-qt-mode: yes --> -<scxml - xmlns="http://www.w3.org/2005/07/scxml" - version="1.0" - name="Directions" - initial="anyplace" -> - <state id="anyplace"> - <transition event="goNowhere" target="nowhere"/> - <transition event="goSomewhere" target="somewhere"/> - - <state id="nowhere"/> - <state id="somewhere"> - <invoke type="http://www.w3.org/TR/scxml/"> - <content> - |scxml name="anywhere" version="1.0"> - <state id="here"> - <transition event="goThere" target="there"/> - </state> - <state id="there"> - <transition event="goHere" target="here"/> - </state> - </scxml> - </content> - </invoke> - </state> - </state> -</scxml> diff --git a/tests/auto/qscxmlc/qscxmlc.pro b/tests/auto/qscxmlc/qscxmlc.pro deleted file mode 100644 index 2024920..0000000 --- a/tests/auto/qscxmlc/qscxmlc.pro +++ /dev/null @@ -1,15 +0,0 @@ -QT = core testlib scxml-private -CONFIG += testcase console c++11 -CONFIG -= app_bundle - -TARGET = tst_qscxmlc -TEMPLATE = app - -RESOURCES += tst_qscxmlc.qrc - -include(../../../tools/qscxmlc/qscxmlc.pri) - -INCLUDEPATH += ../../../tools/qscxmlc/ - -SOURCES += \ - tst_qscxmlc.cpp diff --git a/tests/auto/qscxmlc/tst_qscxmlc.qrc b/tests/auto/qscxmlc/tst_qscxmlc.qrc deleted file mode 100644 index a86c786..0000000 --- a/tests/auto/qscxmlc/tst_qscxmlc.qrc +++ /dev/null @@ -1,43 +0,0 @@ -<RCC> - <qresource prefix="/tst_qscxmlc"> - <file>data/commentInScript.scxml</file> - <file>data/empty.scxml</file> - <file>data/misplacedinvoke.scxml</file> - <file>data/invalidRoot.scxml</file> - <file>data/invalidIf.scxml</file> - <file>data/invalidIf2.scxml</file> - <file>data/invalidAssign.scxml</file> - <file>data/invalidAssign2.scxml</file> - <file>data/invalidXmlHeader.scxml</file> - <file>data/invalidXmlHeader2.scxml</file> - <file>data/elseWithoutIf.scxml</file> - <file>data/elseWithoutIf2.scxml</file> - <file>data/elseWithoutIf3.scxml</file> - <file>data/invalidstatemachinename.scxml</file> - <file>data/invalidScxml.scxml</file> - <file>data/prematureEndOfDocument.scxml</file> - <file>data/elseWithoutIf4.scxml</file> - <file>data/badInitial.scxml</file> - <file>data/elseWithoutIf5.scxml</file> - <file>data/elseWithoutIf6.scxml</file> - <file>data/elseWithoutIf7.scxml</file> - <file>data/elseWithoutIf8.scxml</file> - <file>data/elseWithoutIf9.scxml</file> - <file>data/elseWithoutIf10.scxml</file> - <file>data/invalidContent.scxml</file> - <file>data/invalidInvoke.scxml</file> - <file>data/invalidInvoke2.scxml</file> - <file>data/invalidInvoke3.scxml</file> - <file>data/invalidInvoke4.scxml</file> - <file>data/invalidRoot2.scxml</file> - <file>data/invalidRoot3.scxml</file> - <file>data/invalidRoot4.scxml</file> - <file>data/noContentInInvoke.scxml</file> - <file>data/noContentInInvoke2.scxml</file> - <file>data/noContentInInvoke3.scxml</file> - <file>data/noContentInInvoke4.scxml</file> - <file>data/prematureEndOfDocument2.scxml</file> - <file>data/wrongRoot.scxml</file> - <file>data/nestedScxml.scxml</file> - </qresource> -</RCC> diff --git a/tests/auto/scion/scion.pro b/tests/auto/scion/scion.pro index eccc02d..6e1f7f4 100644 --- a/tests/auto/scion/scion.pro +++ b/tests/auto/scion/scion.pro @@ -28,7 +28,6 @@ defineReplace(nameTheClass) { qtPrepareTool(QMAKE_QSCXMLC, qscxmlc) win32 { - QMAKE_QSCXMLC += --no-c++11 msvc: QMAKE_CXXFLAGS += /bigobj } @@ -52,6 +51,7 @@ ALLSCXMLS = $$files($$SCXMLS_DIR/*.scxml, true) # For a better explanation about the "blacklisted" tests, see tst_scion.cpp # <invoke> BLACKLISTED = \ + test216sub1.scxml \ test226sub1.txml \ test239sub1.scxml \ test242sub1.scxml \ @@ -101,5 +101,3 @@ write_file("scxml/scion.h", contents)|error("Aborting.") contents = '<!DOCTYPE RCC><RCC version=\"1.0\">' '<qresource>' $$qrc '</qresource></RCC>' write_file("scion.qrc", contents)|error("Aborting.") - -load(qscxmlc) diff --git a/tests/auto/scion/tst_scion.cpp b/tests/auto/scion/tst_scion.cpp index 695d1ba..a068005 100644 --- a/tests/auto/scion/tst_scion.cpp +++ b/tests/auto/scion/tst_scion.cpp @@ -30,9 +30,8 @@ #include <QCoreApplication> #include <QJsonDocument> -#include <QtScxml/qscxmlparser.h> +#include <QtScxml/qscxmlcompiler.h> #include <QtScxml/qscxmlecmascriptdatamodel.h> -#include <QtScxml/qscxmlnulldatamodel.h> #include <functional> @@ -55,8 +54,6 @@ static QSet<QString> testFailOnRun = QSet<QString>() << QLatin1String("w3c-ecma/test178.txml") // We do not support the optional basic http event i/o processor. << QLatin1String("w3c-ecma/test201.txml") - << QLatin1String("w3c-ecma/test364.txml") // initial attribute on <state> - << QLatin1String("w3c-ecma/test388.txml") // Qt refuses to set an initial state to a "deep" state << QLatin1String("w3c-ecma/test230.txml") << QLatin1String("w3c-ecma/test250.txml") << QLatin1String("w3c-ecma/test307.txml") @@ -67,8 +64,6 @@ static QSet<QString> testFailOnRun = QSet<QString>() << QLatin1String("w3c-ecma/test456.txml") // replaced by modified_test456 // FIXME: qscxmlc fails on improper scxml file, currently no way of testing it properly for compiled case << QLatin1String("w3c-ecma/test301.txml") - // FIXME: Currently we do not support loading scripts from a srcexpr. - << QLatin1String("w3c-ecma/test216.txml") // FIXME: Currently we do not support nested scxml as a child of assign. << QLatin1String("w3c-ecma/test530.txml") ; @@ -91,45 +86,49 @@ public: } }; -class DynamicLoader: public QScxmlParser::Loader +class DynamicLoader: public QScxmlCompiler::Loader { public: - DynamicLoader(QScxmlParser *parser); - QByteArray load(const QString &name, const QString &baseDir, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; + DynamicLoader(); + QByteArray load(const QString &name, + const QString &baseDir, + QStringList *errors) Q_DECL_OVERRIDE Q_DECL_FINAL; }; -DynamicLoader::DynamicLoader(QScxmlParser *parser) - : Loader(parser) +DynamicLoader::DynamicLoader() + : Loader() {} -QByteArray DynamicLoader::load(const QString &name, const QString &baseDir, bool *ok) +QByteArray DynamicLoader::load(const QString &name, + const QString &baseDir, + QStringList *errors) { - Q_ASSERT(ok != nullptr); - - *ok = false; + QStringList errs; + QByteArray contents; QUrl url(name); if (!url.isLocalFile() && !url.isRelative()) - parser()->addError(QStringLiteral("src attribute is not a local file (%1)").arg(name)); + errs << QStringLiteral("src attribute is not a local file (%1)").arg(name); QFileInfo fInfo = url.isLocalFile() ? url.toLocalFile() : name; if (fInfo.isRelative()) fInfo = QFileInfo(QDir(baseDir).filePath(fInfo.filePath())); fInfo = QFileInfo(QLatin1String(":/") + fInfo.filePath()); // take it from resources if (!fInfo.exists()) { - parser()->addError(QStringLiteral("src attribute resolves to non existing file (%1)").arg(fInfo.absoluteFilePath())); + errs << QStringLiteral("src attribute resolves to non existing file (%1)") + .arg(fInfo.absoluteFilePath()); } else { QFile f(fInfo.absoluteFilePath()); - if (f.open(QFile::ReadOnly)) { - *ok = true; - QByteArray array = f.readAll(); - return array; - } else { - parser()->addError(QStringLiteral("Failure opening file %1: %2") - .arg(fInfo.absoluteFilePath(), f.errorString())); - } + if (f.open(QFile::ReadOnly)) + contents = f.readAll(); + else + errs << QStringLiteral("Failure opening file %1: %2") + .arg(fInfo.absoluteFilePath(), f.errorString()); } - return QByteArray(); + if (errors) + *errors = errs; + + return contents; } @@ -202,18 +201,16 @@ void TestScion::dynamic() QFile scxmlFile(QLatin1String(":/") + scxml); QVERIFY(scxmlFile.open(QIODevice::ReadOnly)); QXmlStreamReader xmlReader(&scxmlFile); - QScxmlParser parser(&xmlReader); - parser.setFileName(scxml); - DynamicLoader loader(&parser); - parser.setLoader(&loader); - parser.parse(); - QVERIFY(parser.errors().isEmpty()); + QScxmlCompiler compiler(&xmlReader); + compiler.setFileName(scxml); + DynamicLoader loader; + compiler.setLoader(&loader); + QScopedPointer<QScxmlStateMachine> stateMachine(compiler.compile()); + QVERIFY(compiler.errors().isEmpty()); scxmlFile.close(); - QScopedPointer<QScxmlStateMachine> stateMachine(parser.instantiateStateMachine()); QVERIFY(stateMachine != Q_NULLPTR); - - parser.instantiateDataModel(stateMachine.data()); + stateMachine->setLoader(&loader); const bool runResult = runTest(stateMachine.data(), testDescription.object()); if (runResult == false && testStatus == TestFailsOnRun) @@ -258,6 +255,8 @@ void TestScion::compiled() QEXPECT_FAIL("", "This is expected to fail", Abort); } QVERIFY(stateMachine != Q_NULLPTR); + DynamicLoader loader; + stateMachine->setLoader(&loader); const bool runResult = runTest(stateMachine.data(), testDescription.object()); if (runResult == false && testStatus == TestFailsOnRun) @@ -382,7 +381,10 @@ bool TestScion::runTest(QScxmlStateMachine *stateMachine, const QJsonObject &tes return playEvents(stateMachine, testDescription); } else { // Wait for all events (delayed or otherwise) to propagate. - finishedSpy.fastWait(); // Some tests don't have a final state, so don't check for the result. + if (stateMachine->isRunning()) { + finishedSpy.fastWait(); // Some tests don't have a final state, so don't check for the + // result + } return verifyStates(stateMachine, testDescription, QLatin1String("initialConfiguration"), 0); } } diff --git a/tests/auto/statemachine/eventoccurred.scxml b/tests/auto/statemachine/eventoccurred.scxml index 87aaf41..c77da54 100644 --- a/tests/auto/statemachine/eventoccurred.scxml +++ b/tests/auto/statemachine/eventoccurred.scxml @@ -35,19 +35,19 @@ <onentry> <raise event="internalEvent1"/> <send event="internalEvent2"/> - <send type="qt:signal" event="externalEvent"/> + <send event="externalEvent"/> <send event="timeout" delay="1s"/> </onentry> <transition event="timeout" target="final"/> </state> <final id="final"/> - </state> - <!-- - The done.state.* events are internal, so expose them to the spy too by re-sending them as - external events: - --> - <transition event="done.state.*" cond="_event.type === 'internal'"> - <send eventexpr="_event.name"/> - </transition> + <!-- + The done.state.* events are internal, so expose them to the spy too by re-sending them as + external events: + --> + <transition event="done.state.*" cond="_event.type === 'internal'"> + <send eventexpr="_event.name"/> + </transition> + </state> </scxml> diff --git a/tests/auto/statemachine/statemachine.pro b/tests/auto/statemachine/statemachine.pro index c96ce21..0e4de1a 100644 --- a/tests/auto/statemachine/statemachine.pro +++ b/tests/auto/statemachine/statemachine.pro @@ -1,4 +1,4 @@ -QT = core gui qml testlib scxml +QT = core gui qml testlib scxml-private CONFIG += testcase TARGET = tst_statemachine @@ -11,5 +11,3 @@ RESOURCES += tst_statemachine.qrc SOURCES += \ tst_statemachine.cpp - -load(qscxmlc) diff --git a/tests/auto/statemachine/tst_statemachine.cpp b/tests/auto/statemachine/tst_statemachine.cpp index fc20118..f09ad42 100644 --- a/tests/auto/statemachine/tst_statemachine.cpp +++ b/tests/auto/statemachine/tst_statemachine.cpp @@ -29,8 +29,9 @@ #include <QtTest> #include <QObject> #include <QXmlStreamReader> -#include <QtScxml/qscxmlparser.h> +#include <QtScxml/qscxmlcompiler.h> #include <QtScxml/qscxmlstatemachine.h> +#include <QtScxml/private/qscxmlstatemachine_p.h> Q_DECLARE_METATYPE(QScxmlError); @@ -45,7 +46,8 @@ private Q_SLOTS: void stateNames(); void activeStateNames_data(); void activeStateNames(); - void connectToFinal(); + void connections(); + void onExit(); void eventOccurred(); void doneDotStateEvent(); @@ -62,18 +64,18 @@ void tst_StateMachine::stateNames_data() << (QStringList() << QString("a1") << QString("a2") << QString("final")); QTest::newRow("stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml") << false - << (QStringList() << QString("a") << QString("a1") << QString("a2") << QString("b") << QString("final") << QString("top")); + << (QStringList() << QString("top") << QString("a") << QString("a1") << QString("a2") << QString("b") << QString("final")); QTest::newRow("stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml") << true << (QStringList() << QString("a") << QString("b")); QTest::newRow("stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml") << false - << (QStringList() << QString("a") << QString("b") << QString("super_top")); + << (QStringList() << QString("super_top") << QString("a") << QString("b")); QTest::newRow("ids1") << QString(":/tst_statemachine/ids1.scxml") << false - << (QStringList() << QString("_") << QString("foo-bar") - << QString("foo.bar") << QString("foo_bar")); + << (QStringList() << QString("foo.bar") << QString("foo-bar") + << QString("foo_bar") << QString("_")); } void tst_StateMachine::stateNames() @@ -100,13 +102,13 @@ void tst_StateMachine::activeStateNames_data() << (QStringList() << QString("a1") << QString("final")); QTest::newRow("stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml") << false - << (QStringList() << QString("a") << QString("a1") << QString("b") << QString("final") << QString("top")); + << (QStringList() << QString("top") << QString("a") << QString("a1") << QString("b") << QString("final")); QTest::newRow("stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml") << true - << (QStringList() << QString("a")<< QString("b")); + << (QStringList() << QString("a") << QString("b")); QTest::newRow("stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml") << false - << (QStringList() << QString("a") << QString("b") << QString("super_top")); + << (QStringList() << QString("super_top") << QString("a") << QString("b")); } void tst_StateMachine::activeStateNames() @@ -127,13 +129,159 @@ void tst_StateMachine::activeStateNames() QCOMPARE(stateMachine->activeStateNames(compressed), expectedStates); } -void tst_StateMachine::connectToFinal() +class Receiver : public QObject { + Q_OBJECT +public slots: + void a(bool enabled) + { + aReached = aReached || enabled; + } + + void b(bool enabled) + { + bReached = bReached || enabled; + } + + void aEnter() + { + aEntered = true; + } + + void aExit() + { + aExited = true; + } + +public: + bool aReached = false; + bool bReached = false; + bool aEntered = false; + bool aExited = false; +}; + +void tst_StateMachine::connections() { - QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(QString(":/tst_statemachine/statenames.scxml"))); + QScopedPointer<QScxmlStateMachine> stateMachine( + QScxmlStateMachine::fromFile(QString(":/tst_statemachine/statenames.scxml"))); QVERIFY(!stateMachine.isNull()); + Receiver receiver; + + bool a1Reached = false; + bool finalReached = false; + QMetaObject::Connection a = stateMachine->connectToState("a", &receiver, &Receiver::a); + QVERIFY(a); + QMetaObject::Connection b = stateMachine->connectToState("b", &receiver, SLOT(b(bool))); + QVERIFY(b); + QMetaObject::Connection a1 = stateMachine->connectToState("a1", &receiver, + [&a1Reached](bool enabled) { + a1Reached = a1Reached || enabled; + }); + QVERIFY(a1); + QMetaObject::Connection final = stateMachine->connectToState("final", + [&finalReached](bool enabled) { + finalReached = finalReached || enabled; + }); + QVERIFY(final); + + bool a1Entered = false; + bool a1Exited = false; + bool finalEntered = false; + bool finalExited = false; + typedef QScxmlStateMachine QXSM; + + QMetaObject::Connection aEntry = stateMachine->connectToState( + "a", QXSM::onEntry(&receiver, &Receiver::aEnter)); + QVERIFY(aEntry); + QMetaObject::Connection aExit = stateMachine->connectToState( + "a", QXSM::onExit(&receiver, &Receiver::aExit)); + QVERIFY(aExit); + QMetaObject::Connection a1Entry = stateMachine->connectToState("a1", &receiver, + QXSM::onEntry([&a1Entered]() { + a1Entered = true; + })); + QVERIFY(a1Entry); + QMetaObject::Connection a1Exit = stateMachine->connectToState("a1", &receiver, + QXSM::onExit([&a1Exited]() { + a1Exited = true; + })); + QVERIFY(a1Exit); + + QMetaObject::Connection finalEntry = stateMachine->connectToState( + "final", QXSM::onEntry([&finalEntered]() { + finalEntered = true; + })); + QVERIFY(finalEntry); + + QMetaObject::Connection finalExit = stateMachine->connectToState( + "final", QXSM::onExit([&finalExited]() { + finalExited = true; + })); + QVERIFY(finalExit); + + stateMachine->start(); + + QTRY_VERIFY(a1Reached); + QTRY_VERIFY(finalReached); + QTRY_VERIFY(receiver.aReached); + QTRY_VERIFY(receiver.bReached); + + QVERIFY(disconnect(a)); + QVERIFY(disconnect(b)); + QVERIFY(disconnect(a1)); + QVERIFY(disconnect(final)); + +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + QVERIFY(receiver.aEntered); + QVERIFY(!receiver.aExited); + QVERIFY(a1Entered); + QVERIFY(!a1Exited); + QVERIFY(finalEntered); + QVERIFY(!finalExited); + + QVERIFY(disconnect(aEntry)); + QVERIFY(disconnect(aExit)); + QVERIFY(disconnect(a1Entry)); + QVERIFY(disconnect(a1Exit)); + QVERIFY(disconnect(finalEntry)); + QVERIFY(disconnect(finalExit)); +#endif +} - QState dummy; - QVERIFY(stateMachine->connectToState(QString("final"), &dummy, SLOT(deleteLater()))); +void tst_StateMachine::onExit() +{ +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + // Test onExit being actually called + + typedef QScxmlStateMachine QXSM; + QScopedPointer<QXSM> stateMachine(QXSM::fromFile(QString(":/tst_statemachine/eventoccurred.scxml"))); + + Receiver receiver; + bool aExited1 = false; + + stateMachine->connectToState("a", QXSM::onExit([&aExited1]() { aExited1 = true; })); + stateMachine->connectToState("a", QXSM::onExit(&receiver, &Receiver::aExit)); + stateMachine->connectToState("a", QXSM::onExit(&receiver, "aEnter")); + { + // Should not crash + Receiver receiver2; + stateMachine->connectToState("a", QXSM::onEntry(&receiver2, &Receiver::aEnter)); + stateMachine->connectToState("a", QXSM::onEntry(&receiver2, "aExit")); + stateMachine->connectToState("a", QXSM::onExit(&receiver2, &Receiver::aExit)); + stateMachine->connectToState("a", QXSM::onExit(&receiver2, "aEnter")); + } + + stateMachine->start(); + QTRY_VERIFY(receiver.aEntered); + QTRY_VERIFY(receiver.aExited); + QTRY_VERIFY(aExited1); +#endif +} + +bool hasChildEventRouters(QScxmlStateMachine *stateMachine) +{ + // Cast to QObject, to avoid ambigous "children" member. + const QObject &parentRouter = QScxmlStateMachinePrivate::get(stateMachine)->m_router; + return !parentRouter.children().isEmpty(); } void tst_StateMachine::eventOccurred() @@ -143,22 +291,63 @@ void tst_StateMachine::eventOccurred() qRegisterMetaType<QScxmlEvent>(); QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished())); - QSignalSpy eventOccurredSpy(stateMachine.data(), SIGNAL(eventOccurred(QScxmlEvent))); - QSignalSpy externalEventOccurredSpy(stateMachine.data(), SIGNAL(externalEventOccurred(QScxmlEvent))); + + int events = 0; + auto con1 = stateMachine->connectToEvent("internalEvent2", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 1); + QCOMPARE(event.name(), QString("internalEvent2")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con1); + + auto con2 = stateMachine->connectToEvent("externalEvent", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 2); + QCOMPARE(event.name(), QString("externalEvent")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con2); + + auto con3 = stateMachine->connectToEvent("timeout", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 3); + QCOMPARE(event.name(), QString("timeout")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con3); + + auto con4 = stateMachine->connectToEvent("done.*", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 4); + QCOMPARE(event.name(), QString("done.state.top")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con4); + + auto con5 = stateMachine->connectToEvent("done.state", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 5); + QCOMPARE(event.name(), QString("done.state.top")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con5); + + auto con6 = stateMachine->connectToEvent("done.state.top", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 6); + QCOMPARE(event.name(), QString("done.state.top")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con6); stateMachine->start(); finishedSpy.wait(5000); + QCOMPARE(events, 6); - QCOMPARE(eventOccurredSpy.count(), 4); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(0).at(0)).name(), QLatin1String("internalEvent2")); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(1).at(0)).name(), QLatin1String("externalEvent")); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(2).at(0)).name(), QLatin1String("timeout")); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(3).at(0)).name(), QLatin1String("done.state.top")); - + QVERIFY(disconnect(con1)); + QVERIFY(disconnect(con2)); + QVERIFY(disconnect(con3)); + QVERIFY(disconnect(con4)); + QVERIFY(disconnect(con5)); + QVERIFY(disconnect(con6)); - QCOMPARE(externalEventOccurredSpy.count(), 1); - QCOMPARE(qvariant_cast<QScxmlEvent>(externalEventOccurredSpy.at(0).at(0)).name(), QLatin1String("externalEvent")); + QTRY_VERIFY(!hasChildEventRouters(stateMachine.data())); } void tst_StateMachine::doneDotStateEvent() diff --git a/tests/auto/qscxmlc/tst_qscxmlc.cpp b/tests/auto/statemachineinfo/statemachine.scxml index 85c2932..94f06b1 100644 --- a/tests/auto/qscxmlc/tst_qscxmlc.cpp +++ b/tests/auto/statemachineinfo/statemachine.scxml @@ -1,3 +1,5 @@ +<?xml version="1.0" ?> +<!-- /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -25,38 +27,19 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ - -#include <QtTest> -#include <QObject> -#include <QCoreApplication> -#include "qscxmlc.h" - -class tst_Qscxmlc: public QObject -{ - Q_OBJECT - -private Q_SLOTS: - void parsing_data(); - void parsing(); -}; - -void tst_Qscxmlc::parsing_data() -{ - QTest::addColumn<QString>("scxmlFileName"); - QDir dir(QLatin1String(":/tst_qscxmlc/data/")); - foreach (const QString &entry, dir.entryList()) { - QTest::newRow(entry.toLatin1().constData()) << dir.filePath(entry); - } -} - -void tst_Qscxmlc::parsing() -{ - QFETCH(QString, scxmlFileName); - QVERIFY(run(QStringList() << QLatin1String("qcsxmlc") << scxmlFileName)); -} - -QTEST_MAIN(tst_Qscxmlc) - -#include "tst_qscxmlc.moc" - - +--> +<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="InfoTest"> + <transition/> + <state> + <transition target="next" event="step"/> + </state> + <parallel id="next"> + <state id="a"> + <transition target="theEnd" event="step"/> + </state> + <state id="b"> + <transition target="theEnd" event="step" type="internal"/> + </state> + </parallel> + <final id="theEnd"/> +</scxml> diff --git a/tests/auto/statemachineinfo/statemachineinfo.pro b/tests/auto/statemachineinfo/statemachineinfo.pro new file mode 100644 index 0000000..f8d5b5d --- /dev/null +++ b/tests/auto/statemachineinfo/statemachineinfo.pro @@ -0,0 +1,13 @@ +QT = core gui qml testlib scxml-private +CONFIG += testcase + +TARGET = tst_statemachineinfo +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +RESOURCES += tst_statemachineinfo.qrc + +SOURCES += \ + tst_statemachineinfo.cpp diff --git a/tests/auto/statemachineinfo/tst_statemachineinfo.cpp b/tests/auto/statemachineinfo/tst_statemachineinfo.cpp new file mode 100644 index 0000000..b1ecfe7 --- /dev/null +++ b/tests/auto/statemachineinfo/tst_statemachineinfo.cpp @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** 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 <QtTest> +#include <QtScxml/qscxmlstatemachine.h> +#include <QtScxml/private/qscxmlstatemachineinfo_p.h> + +Q_DECLARE_METATYPE(QScxmlError); + +class tst_StateMachineInfo: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void checkInfo(); +}; + +class Recorder: public QObject +{ + Q_OBJECT + +public: + void clear() + { + enterCount = 0; + entered.clear(); + exitCount = 0; + exited.clear(); + transitionTriggerCount = 0; + transitions.clear(); + macroStepDone = false; + } + +public slots: + void statesEntered(const QVector<QScxmlStateMachineInfo::StateId> &states) + { entered = states; ++enterCount; } + + void statesExited(const QVector<QScxmlStateMachineInfo::StateId> &states) + { exited = states; ++exitCount; } + + void transitionsTriggered(const QVector<QScxmlStateMachineInfo::TransitionId> &transitions) + { this->transitions = transitions; ++transitionTriggerCount; } + + void reachedStableState() + { macroStepDone = true; } + + bool finishMacroStep() const + { + for (int i = 0; i < 100; ++i) { + if (!macroStepDone) + QCoreApplication::processEvents(); + } + + return macroStepDone; + } + +public: + int enterCount = 0; + QVector<QScxmlStateMachineInfo::StateId> entered; + int exitCount = 0; + QVector<QScxmlStateMachineInfo::StateId> exited; + int transitionTriggerCount = 0; + QVector<QScxmlStateMachineInfo::TransitionId> transitions; + bool macroStepDone = false; +}; + +void tst_StateMachineInfo::checkInfo() +{ + QScopedPointer<QScxmlStateMachine> stateMachine( + QScxmlStateMachine::fromFile(QString(":/tst_statemachineinfo/statemachine.scxml"))); + QVERIFY(!stateMachine.isNull()); + QVERIFY(stateMachine->parseErrors().isEmpty()); + auto info = new QScxmlStateMachineInfo(stateMachine.data()); + + const QString machineName = QLatin1String("InfoTest"); + QCOMPARE(stateMachine->name(), machineName); + + auto states = info->allStates(); + QCOMPARE(states.size(), 5); + QCOMPARE(info->stateName(states.at(0)), QLatin1String("")); + QCOMPARE(info->stateName(states.at(1)), QLatin1String("next")); + QCOMPARE(info->stateName(states.at(2)), QLatin1String("a")); + QCOMPARE(info->stateName(states.at(3)), QLatin1String("b")); + QCOMPARE(info->stateName(states.at(4)), QLatin1String("theEnd")); + + QCOMPARE(info->stateParent(QScxmlStateMachineInfo::InvalidState), + static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); + QCOMPARE(info->stateParent(states.at(0)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); + QCOMPARE(info->stateParent(states.at(1)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); + QCOMPARE(info->stateParent(states.at(2)), 1); + QCOMPARE(info->stateParent(states.at(3)), 1); + QCOMPARE(info->stateParent(states.at(4)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); + + QCOMPARE(info->stateType(states.at(0)), QScxmlStateMachineInfo::NormalState); + QCOMPARE(info->stateType(states.at(1)), QScxmlStateMachineInfo::ParallelState); + QCOMPARE(info->stateType(states.at(2)), QScxmlStateMachineInfo::NormalState); + QCOMPARE(info->stateType(states.at(3)), QScxmlStateMachineInfo::NormalState); + QCOMPARE(info->stateType(states.at(4)), QScxmlStateMachineInfo::FinalState); + + QCOMPARE(info->stateChildren(QScxmlStateMachineInfo::InvalidStateId), + QVector<int>() << 0 << 1 << 4); + QCOMPARE(info->stateChildren(states.at(0)), QVector<int>()); + QCOMPARE(info->stateChildren(states.at(1)), QVector<int>() << 2 << 3); + QCOMPARE(info->stateChildren(states.at(2)), QVector<int>()); + QCOMPARE(info->stateChildren(states.at(3)), QVector<int>()); + QCOMPARE(info->stateChildren(states.at(4)), QVector<int>()); + + QCOMPARE(info->initialTransition(QScxmlStateMachineInfo::InvalidStateId), 4); + QCOMPARE(info->initialTransition(states.at(0)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); + QCOMPARE(info->initialTransition(states.at(1)), 5); + QCOMPARE(info->initialTransition(states.at(2)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); + QCOMPARE(info->initialTransition(states.at(3)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); + QCOMPARE(info->initialTransition(states.at(4)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); + + auto transitions = info->allTransitions(); + QCOMPARE(transitions.size(), 6); + + // targetless transition on top level + QCOMPARE(info->transitionType(transitions.at(0)), QScxmlStateMachineInfo::ExternalTransition); + QCOMPARE(info->stateType(info->transitionSource(transitions.at(0))), + QScxmlStateMachineInfo::InvalidState); + QCOMPARE(info->transitionTargets(transitions.at(0)).size(), 0); + QCOMPARE(info->transitionEvents(transitions.at(0)).size(), 0); + + // <anon>->next + QCOMPARE(info->transitionType(transitions.at(1)), QScxmlStateMachineInfo::ExternalTransition); + QCOMPARE(info->transitionSource(transitions.at(1)), states.at(0)); + QCOMPARE(info->transitionTargets(transitions.at(1)).size(), 1); + QCOMPARE(info->transitionTargets(transitions.at(1)).at(0), states.at(1)); + QCOMPARE(info->transitionEvents(transitions.at(1)).size(), 1); + QCOMPARE(info->transitionEvents(transitions.at(1)).at(0), QStringLiteral("step")); + + // a->theEnd + QCOMPARE(info->transitionType(transitions.at(2)), QScxmlStateMachineInfo::ExternalTransition); + QCOMPARE(info->transitionSource(transitions.at(2)), states.at(2)); + QCOMPARE(info->transitionTargets(transitions.at(2)).size(), 1); + QCOMPARE(info->transitionTargets(transitions.at(2)).at(0), states.at(4)); + QCOMPARE(info->transitionEvents(transitions.at(2)).size(), 1); + QCOMPARE(info->transitionEvents(transitions.at(2)).at(0), QStringLiteral("step")); + + // b->theEnd + QCOMPARE(info->transitionType(transitions.at(3)), QScxmlStateMachineInfo::InternalTransition); + QCOMPARE(info->transitionSource(transitions.at(3)), states.at(3)); + QCOMPARE(info->transitionTargets(transitions.at(3)).size(), 1); + QCOMPARE(info->transitionTargets(transitions.at(3)).at(0), states.at(4)); + QCOMPARE(info->transitionEvents(transitions.at(3)).size(), 1); + QCOMPARE(info->transitionEvents(transitions.at(3)).at(0), QStringLiteral("step")); + + // initial transition that activates the first (anonymous) state + QCOMPARE(info->transitionType(transitions.at(4)), QScxmlStateMachineInfo::SyntheticTransition); + QCOMPARE(info->stateType(info->transitionSource(transitions.at(4))), + QScxmlStateMachineInfo::InvalidState); + QCOMPARE(info->transitionTargets(transitions.at(4)).size(), 1); + QCOMPARE(info->transitionTargets(transitions.at(4)).at(0), states.at(0)); + QCOMPARE(info->transitionEvents(transitions.at(4)).size(), 0); + + // "initial" transition in the next state that activates all sub-states + QCOMPARE(info->transitionType(transitions.at(5)), QScxmlStateMachineInfo::SyntheticTransition); + QCOMPARE(info->transitionSource(transitions.at(5)), states.at(1)); + QCOMPARE(info->transitionTargets(transitions.at(5)).size(), 2); + QCOMPARE(info->transitionTargets(transitions.at(5)).at(0), states.at(2)); + QCOMPARE(info->transitionTargets(transitions.at(5)).at(1), states.at(3)); + QCOMPARE(info->transitionEvents(transitions.at(5)).size(), 0); + + Recorder recorder; + QObject::connect(info, &QScxmlStateMachineInfo::statesEntered, + &recorder, &Recorder::statesEntered); + QObject::connect(info, &QScxmlStateMachineInfo::statesExited, + &recorder, &Recorder::statesExited); + QObject::connect(info, &QScxmlStateMachineInfo::transitionsTriggered, + &recorder, &Recorder::transitionsTriggered); + QObject::connect(stateMachine.data(), &QScxmlStateMachine::reachedStableState, + &recorder, &Recorder::reachedStableState); + + // initial step into first anonymous state + stateMachine->start(); + QVERIFY(recorder.finishMacroStep()); + QCOMPARE(recorder.enterCount, 1); + QCOMPARE(recorder.entered, QVector<QScxmlStateMachineInfo::StateId>() << 0); + QVERIFY(recorder.exited.isEmpty()); + + recorder.clear(); + + // step from anonymous state into the parallel state, which activates "a" and "b" (in THAT + // order!) + stateMachine->submitEvent("step"); + QVERIFY(recorder.finishMacroStep()); + QCOMPARE(recorder.enterCount, 1); + QCOMPARE(recorder.entered, QVector<QScxmlStateMachineInfo::StateId>() << 1 << 2 << 3); + QCOMPARE(recorder.exited, QVector<QScxmlStateMachineInfo::StateId>() << 0); + QCOMPARE(recorder.transitionTriggerCount, 1); + QCOMPARE(recorder.transitions, QVector<QScxmlStateMachineInfo::TransitionId>() << 1); + + recorder.clear(); + + // step from the state "b" into "theEnd", which exits "b", "a", and "next" in exactly that + // order + stateMachine->submitEvent("step"); + QVERIFY(recorder.finishMacroStep()); + QCOMPARE(recorder.enterCount, 1); + QCOMPARE(recorder.entered, QVector<QScxmlStateMachineInfo::StateId>() << 4); + QCOMPARE(recorder.exited, QVector<QScxmlStateMachineInfo::StateId>() << 3 << 2 << 1); + QCOMPARE(recorder.transitionTriggerCount, 1); + QCOMPARE(recorder.transitions, QVector<QScxmlStateMachineInfo::TransitionId>() << 2); +} + + +QTEST_MAIN(tst_StateMachineInfo) + +#include "tst_statemachineinfo.moc" + + diff --git a/tests/auto/statemachineinfo/tst_statemachineinfo.qrc b/tests/auto/statemachineinfo/tst_statemachineinfo.qrc new file mode 100644 index 0000000..0e464d4 --- /dev/null +++ b/tests/auto/statemachineinfo/tst_statemachineinfo.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/tst_statemachineinfo"> + <file>statemachine.scxml</file> + </qresource> +</RCC> diff --git a/tools/qscxmlc/cppdatamodel.t b/tools/qscxmlc/cppdatamodel.t new file mode 100644 index 0000000..2ca5b16 --- /dev/null +++ b/tools/qscxmlc/cppdatamodel.t @@ -0,0 +1,34 @@ +QString ${datamodel}::evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; +${evaluateToStringCases} + Q_UNREACHABLE(); + *ok = false; + return QString(); +} + +bool ${datamodel}::evaluateToBool(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; +${evaluateToBoolCases} + Q_UNREACHABLE(); + *ok = false; + return false; +} + +QVariant ${datamodel}::evaluateToVariant(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; +${evaluateToVariantCases} + Q_UNREACHABLE(); + *ok = false; + return QVariant(); +} + +void ${datamodel}::evaluateToVoid(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; +${evaluateToVoidCases} + Q_UNREACHABLE(); + *ok = false; +} diff --git a/tools/qscxmlc/data.t b/tools/qscxmlc/data.t new file mode 100644 index 0000000..a813d01 --- /dev/null +++ b/tools/qscxmlc/data.t @@ -0,0 +1,135 @@ +struct ${classname}::Data: private QScxmlTableData { + Data(${classname} &stateMachine) + : stateMachine(stateMachine) + {} + + void init() { + stateMachine.setTableData(this); + ${dataModelInitialization} + } + + QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return ${name}; } + + QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return ${initialSetup}; } + + QScxmlExecutableContent::InstructionId *instructions() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return theInstructions; } + + QScxmlExecutableContent::StringId *dataNames(int *count) const Q_DECL_OVERRIDE Q_DECL_FINAL + { *count = ${dataNameCount}; return dataIds; } + + QScxmlExecutableContent::EvaluatorInfo evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const Q_DECL_OVERRIDE Q_DECL_FINAL + { Q_ASSERT(evaluatorId >= 0); Q_ASSERT(evaluatorId < ${evaluatorCount}); return evaluators[evaluatorId]; } + + QScxmlExecutableContent::AssignmentInfo assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const Q_DECL_OVERRIDE Q_DECL_FINAL + { Q_ASSERT(assignmentId >= 0); Q_ASSERT(assignmentId < ${assignmentCount}); return assignments[assignmentId]; } + + QScxmlExecutableContent::ForeachInfo foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const Q_DECL_OVERRIDE Q_DECL_FINAL + { Q_ASSERT(foreachId >= 0); Q_ASSERT(foreachId < ${foreachCount}); return foreaches[foreachId]; } + + QString string(QScxmlExecutableContent::StringId id) const Q_DECL_OVERRIDE Q_DECL_FINAL + { + Q_ASSERT(id >= QScxmlExecutableContent::NoString); Q_ASSERT(id < ${stringCount}); + if (id == QScxmlExecutableContent::NoString) return QString(); + return QString({static_cast<QStringData*>(strings.data + id)}); + } + + const qint32 *stateMachineTable() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return theStateMachineTable; } + + QScxmlInvokableServiceFactory *serviceFactory(int id) const Q_DECL_OVERRIDE Q_DECL_FINAL; + + ${classname} &stateMachine; + ${dataModelField} + + static QScxmlExecutableContent::ParameterInfo param(QScxmlExecutableContent::StringId name, + QScxmlExecutableContent::EvaluatorId expr, + QScxmlExecutableContent::StringId location) + { + QScxmlExecutableContent::ParameterInfo p; + p.name = name; + p.expr = expr; + p.location = location; + return p; + } + + static QScxmlExecutableContent::InvokeInfo invoke( + QScxmlExecutableContent::StringId id, + QScxmlExecutableContent::StringId prefix, + QScxmlExecutableContent::EvaluatorId expr, + QScxmlExecutableContent::StringId location, + QScxmlExecutableContent::StringId context, + QScxmlExecutableContent::ContainerId finalize, + bool autoforward) + { + QScxmlExecutableContent::InvokeInfo i; + i.id = id; + i.prefix = prefix; + i.expr = expr; + i.location = location; + i.context = context; + i.finalize = finalize; + i.autoforward = autoforward; + return i; + } + + static qint32 theInstructions[]; + static QScxmlExecutableContent::StringId dataIds[]; + static QScxmlExecutableContent::EvaluatorInfo evaluators[]; + static QScxmlExecutableContent::AssignmentInfo assignments[]; + static QScxmlExecutableContent::ForeachInfo foreaches[]; + static const qint32 theStateMachineTable[]; + static struct Strings { + QArrayData data[${stringCount}]; + qunicodechar stringdata[${stringdataSize}]; + } strings; +}; + +${classname}::${classname}(QObject *parent) + : QScxmlStateMachine(&staticMetaObject, parent) + , data(new Data(*this)) +{ qRegisterMetaType<${classname} *>(); data->init(); } + +${classname}::~${classname}() +{ delete data; } + +QScxmlInvokableServiceFactory *${classname}::Data::serviceFactory(int id) const +{ +${serviceFactories} +} + +qint32 ${classname}::Data::theInstructions[] = { +${theInstructions} +}; + +QScxmlExecutableContent::StringId ${classname}::Data::dataIds[] = { +${dataIds} +}; + +QScxmlExecutableContent::EvaluatorInfo ${classname}::Data::evaluators[] = { +${evaluators} +}; + +QScxmlExecutableContent::AssignmentInfo ${classname}::Data::assignments[] = { +${assignments} +}; + +QScxmlExecutableContent::ForeachInfo ${classname}::Data::foreaches[] = { +${foreaches} +}; + +#define STR_LIT(idx, ofs, len) \ + Q_STATIC_STRING_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ + qptrdiff(offsetof(Strings, stringdata) + ofs * sizeof(qunicodechar) - idx * sizeof(QArrayData)) \ + ) +${classname}::Data::Strings ${classname}::Data::strings = {{ +${strLits} +},{ +${uniLits} +}}; + +const qint32 ${classname}::Data::theStateMachineTable[] = ${theStateMachineTable}; + +${metaObject} diff --git a/tools/qscxmlc/decl.t b/tools/qscxmlc/decl.t new file mode 100644 index 0000000..9482885 --- /dev/null +++ b/tools/qscxmlc/decl.t @@ -0,0 +1,16 @@ +class ${classname}: public QScxmlStateMachine +{ + /* qmake ignore Q_OBJECT */ + Q_OBJECT +${properties} + +public: + ${classname}(QObject *parent = 0); + ~${classname}(); + +private: + struct Data; + friend struct Data; + struct Data *data; +}; + diff --git a/tools/qscxmlc/doc/qscxmlc.qdoc b/tools/qscxmlc/doc/qscxmlc.qdoc index 619390a..07b5c8d 100644 --- a/tools/qscxmlc/doc/qscxmlc.qdoc +++ b/tools/qscxmlc/doc/qscxmlc.qdoc @@ -65,11 +65,9 @@ \li Option \li Description \row - \li \c --no-c++11 - \li Use no C++11 features in the generated code. - \row \li \c {--namespace <namespace>} - \li Put the generated class(es) in the specified namespace. + \li Put the generated class(es) in the specified namespace. You can use the + \c QSCXMLC_NAMESPACE variable to specify this in your project file. \row \li \c {-o <base/out/name>} \li The base name of the output files. This can include a path. If none is specified, the diff --git a/tools/qscxmlc/generator.cpp b/tools/qscxmlc/generator.cpp index b37e928..9082efa 100644 --- a/tools/qscxmlc/generator.cpp +++ b/tools/qscxmlc/generator.cpp @@ -37,6 +37,7 @@ #include <QtCore/qplugin.h> #include <private/qmetaobject_p.h> //for the flags. +#include <stdio.h> QT_BEGIN_NAMESPACE @@ -1146,13 +1147,13 @@ void Generator::generateStaticMetacall() Q_ASSERT(!f.normalizedType.isEmpty()); fprintf(out, " case %d: ", methodindex); - //---- + //---- Changed from the original in moc if (f.implementation) { fprintf(out, f.implementation, methodindex); fprintf(out, " break;\n"); continue; } - //---- + //---- End of change if (f.normalizedType != "void") fprintf(out, "{ %s _r = ", noRef(f.normalizedType).constData()); @@ -1327,8 +1328,13 @@ void Generator::generateStaticMetacall() fprintf(out, " case %d: *reinterpret_cast<int*>(_v) = QFlag(%s%s()); break;\n", propindex, prefix.constData(), p.read.constData()); else if (!p.read.isEmpty()) - fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s(); break;\n", - propindex, p.type.constData(), prefix.constData(), p.read.constData()); + //---- Changed from the original in moc + { + fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s%s; break;\n", + propindex, p.type.constData(), prefix.constData(), p.read.constData(), + p.read.endsWith(')') ? "" : "()"); + } + //---- End of change else fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s; break;\n", propindex, p.type.constData(), prefix.constData(), p.member.constData()); diff --git a/tools/qscxmlc/generator.h b/tools/qscxmlc/generator.h index b22b9e3..21d82d3 100644 --- a/tools/qscxmlc/generator.h +++ b/tools/qscxmlc/generator.h @@ -30,9 +30,9 @@ #define GENERATOR_H #include "moc.h" -#include <QHash> -#include <QVector> -#include <QIODevice> +#include <QtCore/qhash.h> +#include <QtCore/qvector.h> +#include <QtCore/qiodevice.h> QT_BEGIN_NAMESPACE diff --git a/tools/qscxmlc/main.cpp b/tools/qscxmlc/main.cpp index b401af8..ccc99ed 100644 --- a/tools/qscxmlc/main.cpp +++ b/tools/qscxmlc/main.cpp @@ -30,6 +30,7 @@ #include "qscxmlc.h" #include <QCoreApplication> +#include <QStringList> int main(int argc, char *argv[]) { diff --git a/tools/qscxmlc/moc.h b/tools/qscxmlc/moc.h index 5149454..36cff5f 100644 --- a/tools/qscxmlc/moc.h +++ b/tools/qscxmlc/moc.h @@ -29,13 +29,10 @@ #ifndef MOC_H #define MOC_H -#include <qstringlist.h> -#include <qmap.h> -#include <qpair.h> -#include <qjsondocument.h> -#include <qjsonarray.h> -#include <qjsonobject.h> -#include <stdio.h> +#include <QtCore/qmap.h> +#include <QtCore/qpair.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonarray.h> #include <ctype.h> QT_BEGIN_NAMESPACE diff --git a/tools/qscxmlc/qscxmlc.cpp b/tools/qscxmlc/qscxmlc.cpp index d5217d8..89abdaa 100644 --- a/tools/qscxmlc/qscxmlc.cpp +++ b/tools/qscxmlc/qscxmlc.cpp @@ -26,7 +26,7 @@ ** ****************************************************************************/ -#include <QtScxml/private/qscxmlparser_p.h> +#include <QtScxml/private/qscxmlcompiler_p.h> #include <QtScxml/qscxmltabledata.h> #include "scxmlcppdumper.h" #include "qscxmlc.h" @@ -89,12 +89,12 @@ int write(TranslationUnit *tu) return NoError; } -static void collectAllDocuments(DocumentModel::ScxmlDocument *doc, QMap<DocumentModel::ScxmlDocument *, QString> *docs) +static void collectAllDocuments(DocumentModel::ScxmlDocument *doc, + QList<DocumentModel::ScxmlDocument *> *docs) { - docs->insert(doc, doc->root->name); - foreach (DocumentModel::ScxmlDocument *subDoc, doc->allSubDocuments) { + docs->append(doc); + for (DocumentModel::ScxmlDocument *subDoc : qAsConst(doc->allSubDocuments)) collectAllDocuments(subDoc, docs); - } } int run(const QStringList &arguments) @@ -107,8 +107,6 @@ int run(const QStringList &arguments) cmdParser.setApplicationDescription(QCoreApplication::translate("main", "Compiles the given input.scxml file to a header and a cpp file.")); - QCommandLineOption optionNoCxx11(QLatin1String("no-c++11"), - QCoreApplication::translate("main", "Don't use C++11 in generated code.")); QCommandLineOption optionNamespace(QLatin1String("namespace"), QCoreApplication::translate("main", "Put generated code into <namespace>."), QCoreApplication::translate("main", "namespace")); @@ -124,23 +122,14 @@ int run(const QStringList &arguments) QCommandLineOption optionClassName(QLatin1String("classname"), QCoreApplication::translate("main", "Generate <name> for state machine class name."), QCoreApplication::translate("main", "name")); - QCommandLineOption optionQtMode(QLatin1String("qt-mode"), - QCoreApplication::translate("main", "Enables or disables Qt mode. " - "In order to unconditionally enable qt-mode, specify \"yes\". " - "To unconditionally disable qt-mode, specify \"no\". " - "To read the setting from the input file, specify \"from-input\". " - "The default is \"from-input\"."), - QCoreApplication::translate("main", "mode"), QLatin1String("from-input")); cmdParser.addPositionalArgument(QLatin1String("input"), QCoreApplication::translate("main", "Input SCXML file.")); - cmdParser.addOption(optionNoCxx11); cmdParser.addOption(optionNamespace); cmdParser.addOption(optionOutputBaseName); cmdParser.addOption(optionOutputHeaderName); cmdParser.addOption(optionOutputSourceName); cmdParser.addOption(optionClassName); - cmdParser.addOption(optionQtMode); cmdParser.process(arguments); @@ -160,28 +149,12 @@ int run(const QStringList &arguments) const QString scxmlFileName = inputFiles.at(0); TranslationUnit options; - options.useCxx11 = !cmdParser.isSet(optionNoCxx11); 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); - QString qtModeName = cmdParser.value(optionQtMode); - - QScxmlParser::QtMode qtMode = QScxmlParser::QtModeFromInputFile; - - if (qtModeName == QLatin1String("yes")) { - qtMode = QScxmlParser::QtModeEnabled; - } else if (qtModeName == QLatin1String("no")) { - qtMode = QScxmlParser::QtModeDisabled; - } else if (qtModeName == QLatin1String("from-input")) { - qtMode = QScxmlParser::QtModeFromInputFile; - } else { - errs << QCoreApplication::translate("main", "Error: unexpected value for qt-mode option: %1") - .arg(qtModeName) << endl; - cmdParser.showHelp(CommandLineArgumentsError); - } if (outFileName.isEmpty()) outFileName = QFileInfo(scxmlFileName).baseName(); @@ -197,28 +170,27 @@ int run(const QStringList &arguments) } QXmlStreamReader reader(&file); - QScxmlParser parser(&reader); - parser.setFileName(file.fileName()); - parser.setQtMode(qtMode); - parser.parse(); - if (!parser.errors().isEmpty()) { - foreach (const QScxmlError &error, parser.errors()) { + 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 = QScxmlParserPrivate::get(&parser)->scxmlDocument(); + auto mainDoc = QScxmlCompilerPrivate::get(&compiler)->scxmlDocument(); if (mainDoc == nullptr) { - Q_ASSERT(!parser.errors().isEmpty()); - foreach (const QScxmlError &error, parser.errors()) { + Q_ASSERT(!compiler.errors().isEmpty()); + const auto errors = compiler.errors(); + for (const QScxmlError &error : errors) { errs << error.toString() << endl; } return ScxmlVerificationError; } - QMap<DocumentModel::ScxmlDocument *, QString> docs; - collectAllDocuments(mainDoc, &docs); if (mainClassName.isEmpty()) mainClassName = mainDoc->root->name; if (mainClassName.isEmpty()) { @@ -227,19 +199,33 @@ int run(const QStringList &arguments) if (dot != -1) mainClassName = mainClassName.left(dot); } - docs.insert(mainDoc, mainClassName); + + 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; - for (QMap<DocumentModel::ScxmlDocument *, QString>::const_iterator i = docs.begin(), ei = docs.end(); i != ei; ++i) { - auto name = i.value(); + 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()) { - name = QStringLiteral("%1_StateMachine_%2").arg(mainClassName).arg(tu.classnameForDocument.size() + 1); + prefix = QStringLiteral("%1_StateMachine").arg(mainClassName); + name = prefix; } - tu.classnameForDocument.insert(i.key(), name); + + 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); diff --git a/tools/qscxmlc/qscxmlc.pri b/tools/qscxmlc/qscxmlc.pri index 17afe4b..4c7c991 100644 --- a/tools/qscxmlc/qscxmlc.pri +++ b/tools/qscxmlc/qscxmlc.pri @@ -14,8 +14,8 @@ HEADERS += \ $$PWD/scxmlcppdumper.h HEADERS += \ - $$PWD/../../src/scxml/qscxmlparser.h \ - $$PWD/../../src/scxml/qscxmlparser_p.h \ + $$PWD/../../src/scxml/qscxmlcompiler.h \ + $$PWD/../../src/scxml/qscxmlcompiler_p.h \ $$PWD/../../src/scxml/qscxmlglobals.h \ $$PWD/../../src/scxml/qscxmlexecutablecontent.h \ $$PWD/../../src/scxml/qscxmlexecutablecontent_p.h \ @@ -23,7 +23,7 @@ HEADERS += \ $$PWD/../../src/scxml/qscxmltabledata.h SOURCES += \ - $$PWD/../../src/scxml/qscxmlparser.cpp \ + $$PWD/../../src/scxml/qscxmlcompiler.cpp \ $$PWD/../../src/scxml/qscxmlexecutablecontent.cpp \ $$PWD/../../src/scxml/qscxmlerror.cpp \ $$PWD/../../src/scxml/qscxmltabledata.cpp diff --git a/tools/qscxmlc/qscxmlc.pro b/tools/qscxmlc/qscxmlc.pro index 7621eef..7620d3d 100644 --- a/tools/qscxmlc/qscxmlc.pro +++ b/tools/qscxmlc/qscxmlc.pro @@ -11,3 +11,6 @@ SOURCES += \ main.cpp load(qt_tool) +load(resources) + +RESOURCES += templates.qrc diff --git a/tools/qscxmlc/scxmlcppdumper.cpp b/tools/qscxmlc/scxmlcppdumper.cpp index 46ee774..b539e0d 100644 --- a/tools/qscxmlc/scxmlcppdumper.cpp +++ b/tools/qscxmlc/scxmlcppdumper.cpp @@ -26,18 +26,25 @@ ** ****************************************************************************/ -#include <QtScxml/private/qscxmlexecutablecontent_p.h> #include "scxmlcppdumper.h" +#include "generator.h" + +#include <QtScxml/private/qscxmlexecutablecontent_p.h> + +#include <QtCore/qfileinfo.h> +#include <QtCore/qbuffer.h> +#include <QtCore/qfile.h> +#include <QtCore/qresource.h> #include <algorithm> #include <functional> -#include <QFileInfo> -#include <QBuffer> - -#include "generator.h" QT_BEGIN_NAMESPACE +using namespace QScxmlInternal; + +namespace { + static const QString doNotEditComment = QString::fromLatin1( "//\n" "// Statemachine code from reading SCXML file '%1'\n" @@ -56,91 +63,6 @@ static const QString revisionCheck = QString::fromLatin1( "#endif\n" ); -struct StringListDumper { - StringListDumper &operator <<(const QString &s) { - text.append(s); - return *this; - } - - StringListDumper &operator <<(const QLatin1String &s) { - text.append(s); - return *this; - } - StringListDumper &operator <<(const char *s) { - text.append(QLatin1String(s)); - return *this; - } - StringListDumper &operator <<(int i) { - text.append(QString::number(i)); - return *this; - } - StringListDumper &operator <<(const QByteArray &s) { - text.append(QString::fromUtf8(s)); - return *this; - } - - bool isEmpty() const { - return text.isEmpty(); - } - - void write(QTextStream &out, const QString &prefix, const QString &suffix, const QString &mainClassName = QString()) const - { - foreach (QString line, text) { - if (!mainClassName.isEmpty() && line.contains(QStringLiteral("%"))) { - line = line.arg(mainClassName); - } - out << prefix << line << suffix; - } - } - - void unique() - { - text.sort(); - text.removeDuplicates(); - } - - QStringList text; -}; - -struct Method { - StringListDumper initializer; - Method(const QString &decl = QString()): decl(decl) {} - Method(const StringListDumper &impl): impl(impl) {} - QString decl; // void f(int i = 0); - StringListDumper impl; // void f(int i) { m_i = ++i; } -}; - -struct ClassDump { - bool needsEventFilter; - StringListDumper implIncludes; - QString className; - QString dataModelClassName; - StringListDumper classFields; - StringListDumper tables; - Method init; - Method initDataModel; - StringListDumper dataMethods; - StringListDumper classMethods; - Method constructor; - Method destructor; - StringListDumper properties; - StringListDumper signalMethods; - QList<Method> publicMethods; - QList<Method> protectedMethods; - StringListDumper publicSlotDeclarations; - StringListDumper publicSlotDefinitions; - - QList<Method> dataModelMethods; - - ClassDump() - : needsEventFilter(false) - {} - - QByteArray metaData; -}; - -namespace { - QString cEscape(const QString &str) { QString res; @@ -179,1133 +101,316 @@ QString cEscape(const QString &str) return str; } +typedef QHash<QString, QString> Replacements; +static void genTemplate(QTextStream &out, const QString &filename, const Replacements &replacements) +{ + QResource file(filename); + if (!file.isValid()) { + qFatal("Unable to open template '%s'", qPrintable(filename)); + } + QByteArray data; + if (file.isCompressed() && file.size()) { + data = qUncompress(file.data(), int(file.size())); + } else { + data = QByteArray::fromRawData(reinterpret_cast<const char *>(file.data()), + int(file.size())); + } + const QString t = QString::fromLatin1(data); + data.clear(); + + int start = 0; + for (int openIdx = t.indexOf(QStringLiteral("${"), start); openIdx >= 0; openIdx = + t.indexOf(QStringLiteral("${"), start)) { + out << t.midRef(start, openIdx - start); + openIdx += 2; + const int closeIdx = t.indexOf(QLatin1Char('}'), openIdx); + Q_ASSERT(closeIdx >= openIdx); + QString key = t.mid(openIdx, closeIdx - openIdx); + if (!replacements.contains(key)) { + qFatal("Replacing '%s' failed: no replacement found", qPrintable(key)); + } + out << replacements.value(key); + start = closeIdx + 1; + } + out << t.midRef(start); +} + static const char *headerStart = "#include <QScxmlStateMachine>\n" "#include <QString>\n" - "#include <QByteArray>\n" + "#include <QVariant>\n" "\n"; using namespace DocumentModel; -enum class Evaluator +QString createContainer(const QStringList &elements) { - ToVariant, - ToString, - ToBool, - Assignment, - Foreach, - Script -}; - -class DumperVisitor: public QScxmlExecutableContent::Builder -{ - Q_DISABLE_COPY(DumperVisitor) - -public: - DumperVisitor(ClassDump &clazz, TranslationUnit *tu) - : namespacePrefix(QStringLiteral("::")) - , clazz(clazz) - , translationUnit(tu) - , m_bindLate(false) - , m_qtMode(false) - { - if (!tu->namespaceName.isEmpty()) { - namespacePrefix += QStringLiteral("%1::").arg(tu->namespaceName); - } - } - - void process(ScxmlDocument *doc) - { - Q_ASSERT(doc); - - clazz.className = mangleIdentifier(translationUnit->classnameForDocument.value(doc)); - m_qtMode = doc->qtMode; - - doc->root->accept(this); - - addSubStateMachineProperties(doc); - addEvents(); - - generateMetaObject(); - generateTables(); - } - - ~DumperVisitor() - { - Q_ASSERT(m_parents.isEmpty()); - } - -protected: - using NodeVisitor::visit; - - bool visit(Scxml *node) Q_DECL_OVERRIDE - { - // init: - if (!node->name.isEmpty()) { - clazz.dataMethods << QStringLiteral("QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return string(%1); }").arg(addString(node->name)) - << QString(); - clazz.init.impl << QStringLiteral("stateMachine.setObjectName(string(%1));").arg(addString(node->name)); - } else { - clazz.dataMethods << QStringLiteral("QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return QString(); }") - << QString(); - } - if (node->dataModel == Scxml::CppDataModel) { - // Tell the builder not to generate any script strings when visiting any executable content. - // We'll take care of the evaluators ourselves. - setIsCppDataModel(true); - } - - QString binding; - switch (node->binding) { - case Scxml::EarlyBinding: - binding = QStringLiteral("Early"); - break; - case Scxml::LateBinding: - binding = QStringLiteral("Late"); - m_bindLate = true; - break; - default: - Q_UNREACHABLE(); - } - clazz.init.impl << QStringLiteral("stateMachine.setDataBinding(QScxmlStateMachine::%1Binding);").arg(binding); - clazz.implIncludes << QStringLiteral("qscxmlexecutablecontent.h"); - clazz.init.impl << QStringLiteral("stateMachine.setTableData(this);"); - - foreach (AbstractState *s, node->initialStates) { - clazz.init.impl << QStringLiteral("%1.setAsInitialStateFor(&stateMachine);") - .arg(mangledName(s, StateName)); - } - - // visit the kids: - m_parents.append(node); - visit(node->children); - visit(node->dataElements); - - m_dataElements.append(node->dataElements); - if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return %1; }").arg(startNewSequence()) - << QString(); - generate(m_dataElements); - if (node->script) { - node->script->accept(this); - } - visit(&node->initialSetup); - endSequence(); - } else { - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return QScxmlExecutableContent::NoInstruction; }") - << QString(); - } - - m_parents.removeLast(); - - { // the data model: - switch (node->dataModel) { - case Scxml::NullDataModel: - clazz.classFields << QStringLiteral("QScxmlNullDataModel dataModel;"); - clazz.implIncludes << QStringLiteral("QScxmlNullDataModel"); - clazz.init.impl << QStringLiteral("stateMachine.setDataModel(&dataModel);"); - break; - case Scxml::JSDataModel: - clazz.classFields << QStringLiteral("QScxmlEcmaScriptDataModel dataModel;"); - clazz.implIncludes << QStringLiteral("QScxmlEcmaScriptDataModel"); - clazz.init.impl << QStringLiteral("stateMachine.setDataModel(&dataModel);"); - break; - case Scxml::CppDataModel: - clazz.dataModelClassName = node->cppDataModelClassName; - clazz.implIncludes << node->cppDataModelHeaderName; - break; - default: - Q_UNREACHABLE(); - } - } - return false; - } - - bool visit(State *node) Q_DECL_OVERRIDE - { - QString name = mangledName(node, PlainName); - QString stateName = mangledName(node, StateName); - // Property stuff: - if (isValidQPropertyName(node->id)) { - clazz.properties << QStringLiteral("Q_PROPERTY(bool %1 READ %2 NOTIFY %3)") - .arg(node->id).arg(name) - .arg(mangledName(node, SignalName)); - } - if (m_qtMode) { - Method getter(QStringLiteral("bool %1() const").arg(name)); - getter.impl << QStringLiteral("bool %2::%1() const").arg(name) - << QStringLiteral("{ return data->%1.active(); }").arg(stateName); - clazz.publicMethods << getter; - } - - // Declaration: - if (node->type == State::Final) { - clazz.classFields << QStringLiteral("QScxmlFinalState ") + stateName + QLatin1Char(';'); - } else { - clazz.classFields << QStringLiteral("QScxmlState ") + stateName + QLatin1Char(';'); - } - - // Initializer: - clazz.constructor.initializer << generateInitializer(node); - - // init: - if (!node->id.isEmpty()) { - clazz.init.impl << stateName + QStringLiteral(".setObjectName(string(%1));").arg(addString(node->id)); - } - if (node->type == State::Parallel) { - clazz.init.impl << stateName + QStringLiteral(".setChildMode(QState::ParallelStates);"); - } else { - foreach (AbstractState *initialState, node->initialStates) { - clazz.init.impl << stateName + QStringLiteral(".setInitialState(&") - + mangledName(initialState, StateName) - + QStringLiteral(");"); - } - - } - if (!node->id.isEmpty()) { - clazz.init.impl << QStringLiteral("QObject::connect(&") - + stateName - + QStringLiteral(", SIGNAL(activeChanged(bool)), &stateMachine, SIGNAL(") - + mangledName(node, SignalName) - + QStringLiteral("(bool)));"); - } - - m_stateNames.append(node->id); - m_stateFieldNames.append(stateName); - - // visit the kids: - m_parents.append(node); - if (!node->dataElements.isEmpty()) { - if (m_bindLate) { - clazz.init.impl << stateName + QStringLiteral(".setInitInstructions(%1);").arg(startNewSequence()); - generate(node->dataElements); - endSequence(); - } else { - m_dataElements.append(node->dataElements); - } - } - - visit(node->children); - if (!node->onEntry.isEmpty()) - clazz.init.impl << stateName + QStringLiteral(".setOnEntryInstructions(%1);").arg(generate(node->onEntry)); - if (!node->onExit.isEmpty()) - clazz.init.impl << stateName + QStringLiteral(".setOnExitInstructions(%1);").arg(generate(node->onExit)); - if (!node->invokes.isEmpty()) { - QStringList lines; - for (int i = 0, ei = node->invokes.size(); i != ei; ++i) { - Invoke *invoke = node->invokes.at(i); - QString line = QStringLiteral("new QScxmlInvokeScxmlFactory< %1 >(").arg(scxmlClassName(invoke->content.data())); - line += QStringLiteral("%1, ").arg(Builder::createContext(QStringLiteral("invoke"))); - line += QStringLiteral("%1, ").arg(addString(invoke->id)); - line += QStringLiteral("%1, ").arg(addString(node->id + QStringLiteral(".session-"))); - line += QStringLiteral("%1, ").arg(addString(invoke->idLocation)); - { - QStringList l; - foreach (const QString &name, invoke->namelist) { - l.append(QString::number(addString(name))); - } - line += QStringLiteral("%1, ").arg(createVector(QStringLiteral("QScxmlExecutableContent::StringId"), l)); - } - line += QStringLiteral("%1, ").arg(invoke->autoforward ? QStringLiteral("true") : QStringLiteral("false")); - { - QStringList l; - foreach (DocumentModel::Param *param, invoke->params) { - l += QStringLiteral("QScxmlInvokableServiceFactory::Param(%1, %2, %3)") - .arg(addString(param->name)) - .arg(createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), param->expr)) - .arg(addString(param->location)); - } - line += QStringLiteral("%1, ").arg(createVector(QStringLiteral("QScxmlInvokableServiceFactory::Param"), l)); - } - if (invoke->finalize.isEmpty()) { - line += QStringLiteral("QScxmlExecutableContent::NoInstruction"); - } else { - line += QString::number(startNewSequence()); - visit(&invoke->finalize); - endSequence(); - } - line += QLatin1Char(')'); - lines << line; - } - clazz.init.impl << stateName + QStringLiteral(".setInvokableServiceFactories("); - clazz.init.impl << QStringLiteral(" ") + createVector(QStringLiteral("QScxmlInvokableServiceFactory *"), lines); - clazz.init.impl << QStringLiteral(");"); - } - - if (node->type == State::Final) { - auto id = generate(node->doneData); - clazz.init.impl << stateName + QStringLiteral(".setDoneData(%1);").arg(id); - } - - m_parents.removeLast(); - return false; - } - - bool visit(Transition *node) Q_DECL_OVERRIDE - { - const QString tName = transitionName(node); - if (m_qtMode) { - foreach (const QString &event, node->events) { - if (!DocumentModel::isEventToBeGenerated(event)) - continue; - - // If the event name is not filtered out, is was already validated inside: - // bool ScxmlVerifier::visit(DocumentModel::Transition *transition) - // by a call to: validateEventName(); - m_knownEvents.insert(event); - } - } - - // Declaration: - clazz.classFields << QStringLiteral("QScxmlTransition ") + tName + QLatin1Char(';'); - - // Initializer: - QString initializer = tName + QStringLiteral("("); - QStringList elements; - foreach (const QString &event, node->events) - elements.append(qba(event)); - initializer += createList(QStringLiteral("QString"), elements); - initializer += QStringLiteral(")"); - clazz.constructor.initializer << initializer; - - // init: - if (node->condition) { - QString condExpr = *node->condition.data(); - auto cond = createEvaluatorBool(QStringLiteral("transition"), QStringLiteral("cond"), condExpr); - clazz.init.impl << tName + QStringLiteral(".setConditionalExpression(%1);").arg(cond); - } - - if (m_parents.last()->asHistoryState()) { - clazz.init.impl << QStringLiteral("%1.setDefaultTransition(&%2);").arg(parentStateMemberName(), tName); - } else { - clazz.init.impl << QStringLiteral("%1.addTransitionTo(&%2);").arg(tName, parentStateMemberName()); - } - - if (node->type == Transition::Internal) { - clazz.init.impl << tName + QStringLiteral(".setTransitionType(QAbstractTransition::InternalTransition);"); - } - QStringList targetNames; - foreach (DocumentModel::AbstractState *s, node->targetStates) - targetNames.append(QStringLiteral("&") + mangledName(s, StateName)); - QString targets = tName + QStringLiteral(".setTargetStates(") + createList(QStringLiteral("QAbstractState*"), targetNames); - clazz.init.impl << targets + QStringLiteral(");"); - - // visit the kids: - if (!node->instructionsOnTransition.isEmpty()) { - m_parents.append(node); - m_currentTransitionName = tName; - clazz.init.impl << tName + QStringLiteral(".setInstructionsOnTransition(%1);").arg(startNewSequence()); - visit(&node->instructionsOnTransition); - endSequence(); - m_parents.removeLast(); - m_currentTransitionName.clear(); - } - return false; + QString result; + if (elements.isEmpty()) { + result += QStringLiteral("{}"); + } else { + result += QStringLiteral("{ ") + elements.join(QStringLiteral(", ")) + QStringLiteral(" }"); } + return result; +} - bool visit(DocumentModel::HistoryState *node) Q_DECL_OVERRIDE - { - // Includes: - clazz.implIncludes << "QScxmlHistoryState"; - - const QString stateName = mangledName(node, StateName); - // Declaration: - clazz.classFields << QStringLiteral("QScxmlHistoryState ") + stateName + QLatin1Char(';'); - - // Initializer: - clazz.constructor.initializer << generateInitializer(node); - - // init: - if (!node->id.isEmpty()) { - clazz.init.impl << stateName + QStringLiteral(".setObjectName(string(%1));").arg(addString(node->id)); - } - QString depth; - switch (node->type) { - case DocumentModel::HistoryState::Shallow: - depth = QStringLiteral("Shallow"); - break; - case DocumentModel::HistoryState::Deep: - depth = QStringLiteral("Deep"); +static void generateList(QString &out, std::function<QString(int)> next) +{ + const int maxLineLength = 80; + QString line; + for (int i = 0; ; ++i) { + const QString nr = next(i); + if (nr.isNull()) break; - default: - Q_UNREACHABLE(); - } - clazz.init.impl << stateName + QStringLiteral(".setHistoryType(QScxmlHistoryState::") + depth + QStringLiteral("History);"); - - // visit the kid: - if (Transition *t = node->defaultConfiguration()) { - - m_parents.append(node); - t->accept(this); - m_parents.removeLast(); - } - return false; - } - - bool visit(Send *node) Q_DECL_OVERRIDE - { - if (m_qtMode && node->type == QStringLiteral("qt:signal")) { - if (!m_signals.contains(node->event)) { - m_signals.insert(node->event); - m_signalNames.append(node->event); - clazz.signalMethods << QStringLiteral("void %1(const QVariant &data);").arg(node->event); - } - } - - return QScxmlExecutableContent::Builder::visit(node); - } - -private: - enum NameForm { - PlainName, - SignalName, - MachineName, - StateName - }; - - QString mangledName(const QString &id, NameForm form) const - { - QString name = id; - switch (form) { - case PlainName: break; - case SignalName: name.append(QStringLiteral("Changed")); break; - case StateName: name.prepend(QStringLiteral("state_")); break; - case MachineName: name.prepend(QStringLiteral("machine_")); break; - } - - return name.isEmpty() ? name : mangleIdentifier(name); - } - - QString mangledName(AbstractState *state, NameForm form) const - { - Q_ASSERT(state); - - QString name = m_mangledNames.value(state)[form]; - if (!name.isEmpty()) - return name; - - QString id = state->id; - if (State *s = state->asState()) { - if (s->type == State::Initial) { - id = s->parent->asState()->id + QStringLiteral("_initial"); - } - } - name = mangledName(id, form); - m_mangledNames[state][form] = name; - return name; - } + if (i != 0) + line += QLatin1Char(','); - QString transitionName(Transition *t) const - { - int idx = 0; - QString parentName; - auto parent = m_parents.last(); - if (State *parentState = parent->asState()) { - parentName = mangledName(parentState, PlainName); - idx = childIndex(t, parentState->children); - } else if (HistoryState *historyState = parent->asHistoryState()) { - parentName = mangledName(historyState, PlainName); - } else if (Scxml *scxml = parent->asScxml()) { - parentName = QStringLiteral("stateMachine"); - idx = childIndex(t, scxml->children); - } else { - Q_UNREACHABLE(); + if (line.length() + nr.length() + 1 > maxLineLength) { + out += line + QLatin1Char('\n'); + line.clear(); + } else if (i != 0) { + line += QLatin1Char(' '); } - return QStringLiteral("transition_%1_%2").arg(parentName, QString::number(idx)); + line += nr; } + if (!line.isEmpty()) + out += line; +} - static int childIndex(StateOrTransition *child, const QVector<StateOrTransition *> &children) { - int idx = 0; - foreach (StateOrTransition *sot, children) { - if (sot == child) - break; +void generateTables(const GeneratedTableData &td, Replacements &replacements) +{ + { // instructions + auto instr = td.theInstructions; + QString out; + generateList(out, [&instr](int idx) -> QString { + if (instr.isEmpty() && idx == 0) // prevent generation of empty array + return QStringLiteral("-1"); + if (idx < instr.size()) + return QString::number(instr.at(idx)); else - ++idx; - } - return idx; - } - - QString createList(const QString &elementType, const QStringList &elements) const - { return createContainer(QStringLiteral("QList"), elementType, elements); } - - QString createVector(const QString &elementType, const QStringList &elements) const - { return createContainer(QStringLiteral("QVector"), elementType, elements); } - - QString createContainer(const QString &baseType, const QString &elementType, const QStringList &elements) const - { - QString result; - if (translationUnit->useCxx11) { - if (elements.isEmpty()) { - result += QStringLiteral("{}"); - } else { - result += QStringLiteral("{ ") + elements.join(QStringLiteral(", ")) + QStringLiteral(" }"); - } - } else { - result += QStringLiteral("%1< %2 >()").arg(baseType, elementType); - if (!elements.isEmpty()) { - result += QStringLiteral(" << ") + elements.join(QStringLiteral(" << ")); - } - } - return result; - } - - QString generateInitializer(AbstractState *node) const - { - QString init = mangledName(node, StateName) + QStringLiteral("("); - if (State *parentState = node->parent->asState()) { - init += QStringLiteral("&") + mangledName(parentState, StateName); - } else { - init += QStringLiteral("&stateMachine"); - } - init += QLatin1Char(')'); - return init; - } - - void addSubStateMachineProperties(ScxmlDocument *doc) - { - foreach (ScxmlDocument *subDocs, doc->allSubDocuments) { - QString name = subDocs->root->name; - if (name.isEmpty()) - continue; - auto plainName = mangledName(name, PlainName); - auto qualifiedName = namespacePrefix + plainName; - if (m_serviceProps.contains(qMakePair(plainName, qualifiedName))) - continue; - m_serviceProps.append(qMakePair(name, qualifiedName)); - clazz.classFields << QStringLiteral("%1 *%2;").arg(qualifiedName, plainName); - clazz.constructor.initializer << QStringLiteral("%1(Q_NULLPTR)").arg(plainName); - if (isValidQPropertyName(name)) { - clazz.properties << QStringLiteral("Q_PROPERTY(%1%2 *%3 READ %2 NOTIFY %4)") - .arg(namespacePrefix, plainName, name, - mangledName(name, SignalName)); - } - if (m_qtMode) { - Method getter(QStringLiteral("%1 *%2() const").arg(qualifiedName, plainName)); - getter.impl << QStringLiteral("%1 *%2::%3() const").arg(qualifiedName, - clazz.className, plainName) - << QStringLiteral("{ return data->%1; }").arg(plainName); - clazz.publicMethods << getter; - clazz.signalMethods << QStringLiteral("void %1(%2 *statemachine);") - .arg(mangledName(name, SignalName), qualifiedName); - } - - clazz.dataMethods << QStringLiteral("%1 *%2() const") - .arg(qualifiedName, mangledName(name, MachineName)) - << QStringLiteral("{ return %1; }").arg(plainName) - << QString(); - } - } - - void addEvents() - { - QStringList knownEventsList = m_knownEvents.toList(); - std::sort(knownEventsList.begin(), knownEventsList.end()); - if (m_qtMode) { - foreach (const QString &event, knownEventsList) { - clazz.publicSlotDeclarations << QStringLiteral("void ") + event + QStringLiteral("(const QVariant &eventData = QVariant());"); - clazz.publicSlotDefinitions << QStringLiteral("void ") + clazz.className - + QStringLiteral("::") - + event - + QStringLiteral("(const QVariant &eventData)\n{ submitEvent(data->") + qba(event) - + QStringLiteral(", eventData); }"); - } - } - - if (!m_signalNames.isEmpty()) { - clazz.needsEventFilter = true; - clazz.init.impl << QStringLiteral("stateMachine.setScxmlEventFilter(this);"); - auto &dm = clazz.dataMethods; - dm << QStringLiteral("bool handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) Q_DECL_OVERRIDE {"); - if (m_qtMode) { - dm << QStringLiteral(" if (event->originType() != QStringLiteral(\"qt:signal\")) { return true; }") - << QStringLiteral(" %1 *m = static_cast< %1 * >(stateMachine);").arg(clazz.className); - foreach (const QString &signalName, m_signalNames) { - dm << QStringLiteral(" if (event->name() == %1) { emit m->%2(event->data()); return false; }") - .arg(qba(signalName), mangleIdentifier(signalName)); - } - } - dm << QStringLiteral(" return true;") - << QStringLiteral("}") - << QString(); - } - } - - QString createContextString(const QString &instrName) const Q_DECL_OVERRIDE - { - if (!m_currentTransitionName.isEmpty()) { - QString state = parentStateName(); - return QStringLiteral("<%1> instruction in transition of state '%2'").arg(instrName, state); - } else { - return QStringLiteral("<%1> instruction in state '%2'").arg(instrName, parentStateName()); - } - } - - QString createContext(const QString &instrName, const QString &attrName, const QString &attrValue) const Q_DECL_OVERRIDE - { - QString location = createContextString(instrName); - return QStringLiteral("%1 with %2=\"%3\"").arg(location, attrName, attrValue); - } - - QString parentStateName() const - { - for (int i = m_parents.size() - 1; i >= 0; --i) { - Node *node = m_parents.at(i); - if (State *s = node->asState()) - return s->id; - else if (HistoryState *h = node->asHistoryState()) - return h->id; - else if (Scxml *l = node->asScxml()) - return l->name; - } - - return QString(); - } - - QString parentStateMemberName() const - { - Node *parent = m_parents.last(); - if (State *s = parent->asState()) - return mangledName(s, StateName); - else if (HistoryState *h = parent->asHistoryState()) - return mangledName(h, StateName); - else if (parent->asScxml()) - return QStringLiteral("stateMachine"); - else - Q_UNIMPLEMENTED(); - return QString(); - } - - static void generateList(StringListDumper &t, std::function<QString(int)> next) - { - const int maxLineLength = 80; - QString line; - for (int i = 0; ; ++i) { - QString nr = next(i); - if (nr.isNull()) - break; - + return QString(); + }); + replacements[QStringLiteral("theInstructions")] = out; + } + + { // dataIds + auto dataIds = td.theDataNameIds; + QString out; + generateList(out, [&dataIds](int idx) -> QString { + if (dataIds.size() == 0 && idx == 0) // prevent generation of empty array + return QStringLiteral("-1"); + if (idx < dataIds.size()) + return QString::number(dataIds[idx]); + else + return QString(); + }); + replacements[QStringLiteral("dataNameCount")] = QString::number(dataIds.size()); + replacements[QStringLiteral("dataIds")] = out; + } + + { // evaluators + auto evaluators = td.theEvaluators; + QString out; + generateList(out, [&evaluators](int idx) -> QString { + if (evaluators.isEmpty() && idx == 0) // prevent generation of empty array + return QStringLiteral("{ -1, -1 }"); + if (idx >= evaluators.size()) + return QString(); + + const auto eval = evaluators.at(idx); + return QStringLiteral("{ %1, %2 }").arg(eval.expr).arg(eval.context); + }); + replacements[QStringLiteral("evaluatorCount")] = QString::number(evaluators.size()); + replacements[QStringLiteral("evaluators")] = out; + } + + { // assignments + auto assignments = td.theAssignments; + QString out; + generateList(out, [&assignments](int idx) -> QString { + if (assignments.isEmpty() && idx == 0) // prevent generation of empty array + return QStringLiteral("{ -1, -1, -1 }"); + if (idx >= assignments.size()) + return QString(); + + auto assignment = assignments.at(idx); + return QStringLiteral("{ %1, %2, %3 }") + .arg(assignment.dest).arg(assignment.expr).arg(assignment.context); + }); + replacements[QStringLiteral("assignmentCount")] = QString::number(assignments.size()); + replacements[QStringLiteral("assignments")] = out; + } + + { // foreaches + auto foreaches = td.theForeaches; + QString out; + generateList(out, [&foreaches](int idx) -> QString { + if (foreaches.isEmpty() && idx == 0) // prevent generation of empty array + return QStringLiteral("{ -1, -1, -1, -1 }"); + if (idx >= foreaches.size()) + return QString(); + + auto foreachItem = foreaches.at(idx); + return QStringLiteral("{ %1, %2, %3, %4 }").arg(foreachItem.array).arg(foreachItem.item) + .arg(foreachItem.index).arg(foreachItem.context); + }); + replacements[QStringLiteral("foreachCount")] = QString::number(foreaches.size()); + replacements[QStringLiteral("foreaches")] = out; + } + + { // strings + QString out; + auto strings = td.theStrings; + if (strings.isEmpty()) // prevent generation of empty array + strings.append(QStringLiteral("")); + int ucharCount = 0; + generateList(out, [&ucharCount, &strings](int idx) -> QString { + if (idx >= strings.size()) + return QString(); + + const int length = strings.at(idx).size(); + const QString str = QStringLiteral("STR_LIT(%1, %2, %3)").arg( + QString::number(idx), QString::number(ucharCount), QString::number(length)); + ucharCount += length + 1; + return str; + }); + replacements[QStringLiteral("stringCount")] = QString::number(strings.size()); + replacements[QStringLiteral("strLits")] = out; + + out.clear(); + for (int i = 0, ei = strings.size(); i < ei; ++i) { + const QString &string = strings.at(i); + QString result; if (i != 0) - line += QLatin1Char(','); - - if (line.length() + nr.length() + 1 > maxLineLength) { - t << line; - line.clear(); - } else if (i != 0) { - line += QLatin1Char(' '); + result += QLatin1Char('\n'); + for (int charPos = 0, eCharPos = string.size(); charPos < eCharPos; ++charPos) { + result.append(QStringLiteral("0x%1,") + .arg(QString::number(string.at(charPos).unicode(), 16))); } - line += nr; + result.append(QStringLiteral("0%1 // %2: %3") + .arg(QLatin1String(i < ei - 1 ? "," : ""), QString::number(i), + cEscape(string))); + out += result; } - if (!line.isEmpty()) - t << line; + replacements[QStringLiteral("uniLits")] = out; + replacements[QStringLiteral("stringdataSize")] = QString::number(ucharCount + 1); } +} - void generateTables() - { - StringListDumper &t = clazz.tables; - clazz.classFields << QString(); - QScopedPointer<QScxmlExecutableContent::DynamicTableData> td(tableData()); - - { // instructions - clazz.classFields << QStringLiteral("static qint32 theInstructions[];"); - t << QStringLiteral("qint32 %1::Data::theInstructions[] = {").arg(clazz.className); - auto instr = td->instructionTable(); - generateList(t, [&instr](int idx) -> QString { - if (instr.isEmpty() && idx == 0) // prevent generation of empty array - return QStringLiteral("-1"); - if (idx < instr.size()) - return QString::number(instr.at(idx)); - else - return QString(); - }); - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::Instructions instructions() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return theInstructions; }") - << QString(); - } - - { // dataIds - int count; - auto dataIds = td->dataNames(&count); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::StringId dataIds[];"); - t << QStringLiteral("QScxmlExecutableContent::StringId %1::Data::dataIds[] = {").arg(clazz.className); - if (isCppDataModel()) { - t << QStringLiteral("-1"); - } else { - generateList(t, [&dataIds, count](int idx) -> QString { - if (count == 0 && idx == 0) // prevent generation of empty array - return QStringLiteral("-1"); - if (idx < count) - return QString::number(dataIds[idx]); - else - return QString(); - }); - } - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::StringId *dataNames(int *count) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ *count = %1; return dataIds; }").arg(count); - clazz.dataMethods << QString(); - } - - { // evaluators - auto evaluators = td->evaluators(); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::EvaluatorInfo evaluators[];"); - t << QStringLiteral("QScxmlExecutableContent::EvaluatorInfo %1::Data::evaluators[] = {").arg(clazz.className); - if (isCppDataModel()) { - t << QStringLiteral("{ -1, -1 }"); - } else { - generateList(t, [&evaluators](int idx) -> QString { - if (evaluators.isEmpty() && idx == 0) // prevent generation of empty array - return QStringLiteral("{ -1, -1 }"); - if (idx >= evaluators.size()) - return QString(); - - auto eval = evaluators.at(idx); - return QStringLiteral("{ %1, %2 }").arg(eval.expr).arg(eval.context); - }); - } - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::EvaluatorInfo evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ Q_ASSERT(evaluatorId >= 0); Q_ASSERT(evaluatorId < %1); return evaluators[evaluatorId]; }").arg(evaluators.size()); - clazz.dataMethods << QString(); - - if (isCppDataModel()) { - { - StringListDumper stringEvals; - stringEvals << QStringLiteral("QString %1::evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = stringEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - stringEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" return [this]()->QString{ return %1; }();").arg(it.value()); - } - stringEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" return QString();") - << QStringLiteral(" }"); - stringEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(stringEvals)); - } - - { - StringListDumper boolEvals; - boolEvals << QStringLiteral("bool %1::evaluateToBool(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = boolEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - boolEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" return [this]()->bool{ return %1; }();").arg(it.value()); - } - boolEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" return false;") - << QStringLiteral(" }"); - boolEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(boolEvals)); - } - - { - StringListDumper variantEvals; - variantEvals << QStringLiteral("QVariant %1::evaluateToVariant(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = variantEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - variantEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" return [this]()->QVariant{ return %1; }();").arg(it.value()); - } - variantEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" return QVariant();") - << QStringLiteral(" }"); - variantEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(variantEvals)); - } - - { - StringListDumper voidEvals; - voidEvals << QStringLiteral("void %1::evaluateToVoid(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = voidEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - voidEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" [this]()->void{ %1 }();").arg(it.value()) - << QStringLiteral(" break;"); - } - voidEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" }"); - voidEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(voidEvals)); - } - } - } - - { // assignments - auto assignments = td->assignments(); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::AssignmentInfo assignments[];"); - t << QStringLiteral("QScxmlExecutableContent::AssignmentInfo %1::Data::assignments[] = {").arg(clazz.className); - if (isCppDataModel()) { - t << QStringLiteral("{ -1, -1, -1 }"); - } else { - generateList(t, [&assignments](int idx) -> QString { - if (assignments.isEmpty() && idx == 0) // prevent generation of empty array - return QStringLiteral("{ -1, -1, -1 }"); - if (idx >= assignments.size()) - return QString(); - - auto ass = assignments.at(idx); - return QStringLiteral("{ %1, %2, %3 }").arg(ass.dest).arg(ass.expr).arg(ass.context); - }); - } - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::AssignmentInfo assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ Q_ASSERT(assignmentId >= 0); Q_ASSERT(assignmentId < %1); return assignments[assignmentId]; }").arg(assignments.size()); - clazz.dataMethods << QString(); - } - - { // foreaches - auto foreaches = td->foreaches(); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::ForeachInfo foreaches[];"); - t << QStringLiteral("QScxmlExecutableContent::ForeachInfo %1::Data::foreaches[] = {").arg(clazz.className); - generateList(t, [&foreaches](int idx) -> QString { - if (foreaches.isEmpty() && idx == 0) // prevent generation of empty array - return QStringLiteral("{ -1, -1, -1, -1 }"); - if (idx >= foreaches.size()) - return QString(); - - auto foreach = foreaches.at(idx); - return QStringLiteral("{ %1, %2, %3, %4 }").arg(foreach.array).arg(foreach.item).arg(foreach.index).arg(foreach.context); - }); - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::ForeachInfo foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ Q_ASSERT(foreachId >= 0); Q_ASSERT(foreachId < %1); return foreaches[foreachId]; }").arg(foreaches.size()); - } - - { // strings - t << QStringLiteral("#define STR_LIT(idx, ofs, len) \\") - << QStringLiteral(" Q_STATIC_STRING_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \\") - << QStringLiteral(" qptrdiff(offsetof(Strings, stringdata) + ofs * sizeof(qunicodechar) - idx * sizeof(QArrayData)) \\") - << QStringLiteral(" )"); - - t << QStringLiteral("%1::Data::Strings %1::Data::strings = {{").arg(clazz.className); - auto strings = td->stringTable(); - if (strings.isEmpty()) // prevent generation of empty array - strings.append(QStringLiteral("")); - int ucharCount = 0; - generateList(t, [&ucharCount, &strings](int idx) -> QString { - if (idx >= strings.size()) - return QString(); - - int length = strings.at(idx).size(); - QString str = QStringLiteral("STR_LIT(%1, %2, %3)").arg(QString::number(idx), - QString::number(ucharCount), - QString::number(length)); - ucharCount += length + 1; - return str; - }); - t << QStringLiteral("},{"); - for (int i = 0, ei = strings.size(); i < ei; ++i) { - const QString &string = strings.at(i); - QString result; - for (int charPos = 0, eCharPos = string.size(); charPos < eCharPos; ++charPos) { - result.append(QStringLiteral("0x%1,") - .arg(QString::number(string.at(charPos).unicode(), 16))); - } - result.append(QStringLiteral("0%1 // %2: %3") - .arg(QLatin1String(i < ei - 1 ? "," : ""), QString::number(i), - cEscape(string))); - t << result; - } - t << QStringLiteral("}};") << QStringLiteral(""); - - clazz.classFields << QStringLiteral("static struct Strings {") - << QStringLiteral(" QArrayData data[%1];").arg(strings.size()) - << QStringLiteral(" qunicodechar stringdata[%1];").arg(ucharCount + 1) - << QStringLiteral("} strings;"); - - clazz.dataMethods << QStringLiteral("QString string(QScxmlExecutableContent::StringId id) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{"); - clazz.dataMethods << QStringLiteral(" Q_ASSERT(id >= QScxmlExecutableContent::NoString); Q_ASSERT(id < %1);").arg(strings.size()); - clazz.dataMethods << QStringLiteral(" if (id == QScxmlExecutableContent::NoString) return QString();"); - if (translationUnit->useCxx11) { - clazz.dataMethods << QStringLiteral(" return QString({static_cast<QStringData*>(strings.data + id)});"); - } else { - clazz.dataMethods << QStringLiteral(" QStringDataPtr data;"); - clazz.dataMethods << QStringLiteral(" data.ptr = static_cast<QStringData*>(strings.data + id);"); - clazz.dataMethods << QStringLiteral(" return QString(data);"); - } - clazz.dataMethods << QStringLiteral("}"); - clazz.dataMethods << QString(); - } - } +void generateCppDataModelEvaluators(const GeneratedTableData::DataModelInfo &info, + Replacements &replacements) +{ + const QString switchStart = QStringLiteral(" switch (id) {\n"); + const QString switchEnd = QStringLiteral(" default: break;\n }"); + const QString unusedId = QStringLiteral(" Q_UNUSED(id);"); + QString stringEvals; + if (!info.stringEvaluators.isEmpty()) { + stringEvals += switchStart; + for (auto it = info.stringEvaluators.constBegin(), eit = info.stringEvaluators.constEnd(); + it != eit; ++it) { + stringEvals += QStringLiteral(" case %1:\n").arg(it.key()); + stringEvals += QStringLiteral(" return [this]()->QString{ return %1; }();\n") + .arg(it.value()); + } + stringEvals += switchEnd; + } else { + stringEvals += unusedId; + } + replacements[QStringLiteral("evaluateToStringCases")] = stringEvals; + + QString boolEvals; + if (!info.boolEvaluators.isEmpty()) { + boolEvals += switchStart; + for (auto it = info.boolEvaluators.constBegin(), eit = info.boolEvaluators.constEnd(); + it != eit; ++it) { + boolEvals += QStringLiteral(" case %1:\n").arg(it.key()); + boolEvals += QStringLiteral(" return [this]()->bool{ return %1; }();\n") + .arg(it.value()); + } + boolEvals += switchEnd; + } else { + boolEvals += unusedId; + } + replacements[QStringLiteral("evaluateToBoolCases")] = boolEvals; + + QString variantEvals; + if (!info.variantEvaluators.isEmpty()) { + variantEvals += switchStart; + for (auto it = info.variantEvaluators.constBegin(), eit = info.variantEvaluators.constEnd(); + it != eit; ++it) { + variantEvals += QStringLiteral(" case %1:\n").arg(it.key()); + variantEvals += QStringLiteral(" return [this]()->QVariant{ return %1; }();\n") + .arg(it.value()); + } + variantEvals += switchEnd; + } else { + variantEvals += unusedId; + } + replacements[QStringLiteral("evaluateToVariantCases")] = variantEvals; + + QString voidEvals; + if (!info.voidEvaluators.isEmpty()) { + voidEvals = switchStart; + for (auto it = info.voidEvaluators.constBegin(), eit = info.voidEvaluators.constEnd(); + it != eit; ++it) { + voidEvals += QStringLiteral(" case %1:\n").arg(it.key()); + voidEvals += QStringLiteral(" [this]()->void{ %1 }();\n").arg(it.value()); + voidEvals += QStringLiteral(" return;\n"); + } + voidEvals += switchEnd; + } else { + voidEvals += unusedId; + } + replacements[QStringLiteral("evaluateToVoidCases")] = voidEvals; +} - void generateMetaObject() +int createFactoryId(QStringList &factories, const QString &className, + const QString &namespacePrefix, + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &namelist, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters) +{ + const int idx = factories.size(); + + QString line = QStringLiteral("case %1: return new ").arg(QString::number(idx)); + if (invokeInfo.expr == QScxmlExecutableContent::NoInstruction) { + line += QStringLiteral("QScxmlStaticScxmlServiceFactory< %1::%2 >(") + .arg(namespacePrefix, className); + } else { + line += QStringLiteral("QScxmlDynamicScxmlServiceFactory("); + } + line += QStringLiteral("invoke(%1, %2, %3, %4, %5, %6, %7), ") + .arg(QString::number(invokeInfo.id), + QString::number(invokeInfo.prefix), + QString::number(invokeInfo.expr), + QString::number(invokeInfo.location), + QString::number(invokeInfo.context), + QString::number(invokeInfo.finalize)) + .arg(invokeInfo.autoforward ? QStringLiteral("true") : QStringLiteral("false")); { - ClassDef classDef; - classDef.classname = clazz.className.toUtf8(); - classDef.qualified = classDef.classname; - classDef.superclassList << qMakePair(QByteArray("QScxmlStateMachine"), FunctionDef::Public); - classDef.hasQObject = true; - - // Event signals: - foreach (const QString &signalName, m_signalNames) { - FunctionDef signal; - signal.type.name = "void"; - signal.type.rawName = signal.type.name; - signal.normalizedType = signal.type.name; - signal.name = signalName.toUtf8(); - signal.access = FunctionDef::Public; - signal.isSignal = true; - - ArgumentDef arg; - arg.type.name = "const QVariant &"; - arg.type.rawName = arg.type.name; - arg.normalizedType = "QVariant"; - arg.name = "data"; - arg.typeNameForCast = arg.normalizedType + "*"; - signal.arguments << arg; - - classDef.signalList << signal; - } - - // stateNames: - foreach (const QString &stateName, m_stateNames) { - if (stateName.isEmpty()) - continue; - - const QByteArray mangledStateName = mangledName(stateName, StateName).toUtf8(); - const QString mangledSignalName = mangledName(stateName, SignalName); - - FunctionDef signal; - signal.type.name = "void"; - signal.type.rawName = signal.type.name; - signal.normalizedType = signal.type.name; - signal.name = mangledSignalName.toUtf8(); - signal.access = FunctionDef::Private; - signal.isSignal = true; - if (!m_qtMode) { - signal.implementation = "QMetaObject::activate(_o, &staticMetaObject, %d, _a);"; - } else { - clazz.signalMethods << QStringLiteral("void %1(bool active);") - .arg(mangledSignalName); - } - ArgumentDef arg; - arg.type.name = "bool"; - arg.type.rawName = arg.type.name; - arg.normalizedType = arg.type.name; - arg.name = "active"; - arg.typeNameForCast = arg.type.name + "*"; - signal.arguments << arg; - classDef.signalList << signal; - - ++classDef.notifyableProperties; - PropertyDef prop; - prop.name = stateName.toUtf8(); - prop.type = "bool"; - prop.read = "data->" + mangledStateName + ".active"; - prop.notify = mangledSignalName.toUtf8(); - prop.notifyId = classDef.signalList.size() - 1; - prop.gspec = PropertyDef::ValueSpec; - prop.scriptable = "true"; - classDef.propertyList << prop; - } - - // event slots: - foreach (const QString &eventName, m_knownEvents) { - FunctionDef slot; - slot.type.name = "void"; - slot.type.rawName = slot.type.name; - slot.normalizedType = slot.type.name; - slot.name = eventName.toUtf8(); - slot.access = FunctionDef::Public; - slot.isSlot = true; - - classDef.slotList << slot; - - ArgumentDef arg; - arg.type.name = "const QVariant &"; - arg.type.rawName = arg.type.name; - arg.normalizedType = "QVariant"; - arg.name = "data"; - arg.typeNameForCast = arg.normalizedType + "*"; - slot.arguments << arg; - - classDef.slotList << slot; + QStringList l; + for (auto name : namelist) { + l.append(QString::number(name)); } - - // sub-statemachines: - QHash<QByteArray, QByteArray> knownQObjectClasses; - knownQObjectClasses.insert(QByteArray("QScxmlStateMachine"), QByteArray()); - Method reg(QStringLiteral("void setService(const QString &id, QScxmlInvokableService *service) Q_DECL_OVERRIDE Q_DECL_FINAL")); - reg.impl << QStringLiteral("void %1::setService(const QString &id, QScxmlInvokableService *service) {").arg(clazz.className); - if (m_serviceProps.isEmpty()) { - reg.impl << QStringLiteral(" Q_UNUSED(id);") - << QStringLiteral(" Q_UNUSED(service);"); - } - for (const auto &service : m_serviceProps) { - auto serviceName = service.first; - const QString mangledServiceName = mangledName(serviceName, PlainName); - const QString fqServiceClass = service.second; - const QByteArray serviceClass = fqServiceClass.toUtf8(); - knownQObjectClasses.insert(serviceClass, ""); - - reg.impl << QStringLiteral(" SET_SERVICE_PROP(%1, %2, %3%2, %4)") - .arg(addString(serviceName)) - .arg(mangledServiceName, namespacePrefix).arg(classDef.signalList.size()); - - const QByteArray mangledMachineName = mangledName(serviceName, MachineName).toUtf8(); - const QByteArray mangledSignalName = mangledName(serviceName, SignalName).toUtf8(); - - FunctionDef signal; - signal.type.name = "void"; - signal.type.rawName = signal.type.name; - signal.normalizedType = signal.type.name; - signal.name = mangledSignalName; - signal.access = FunctionDef::Private; - signal.isSignal = true; - if (!m_qtMode) { - signal.implementation = "QMetaObject::activate(_o, &staticMetaObject, %d, _a);"; - } - ArgumentDef arg; - arg.type.name = serviceClass + " *"; - arg.type.rawName = arg.type.name; - arg.type.referenceType = Type::Pointer; - arg.normalizedType = serviceClass + "*(*)"; - arg.name = "statemachine"; - arg.typeNameForCast = arg.type.name + "*"; - signal.arguments << arg; - classDef.signalList << signal; - - ++classDef.notifyableProperties; - PropertyDef prop; - prop.name = serviceName.toUtf8(); - prop.type = serviceClass + "*"; - prop.read = "data->" + mangledMachineName; - prop.notify = mangledSignalName; - prop.notifyId = classDef.signalList.size() - 1; - prop.gspec = PropertyDef::ValueSpec; - prop.scriptable = "true"; - classDef.propertyList << prop; - } - reg.impl << QStringLiteral("}"); - clazz.protectedMethods.append(reg); - - QBuffer buf(&clazz.metaData); - buf.open(QIODevice::WriteOnly); - Generator(&classDef, QList<QByteArray>(), knownQObjectClasses, - QHash<QByteArray, QByteArray>(), buf).generateCode(); - buf.close(); - } - - QString qba(const QString &bytes) - { - return QStringLiteral("string(%1)").arg(addString(bytes)); - } - - QString scxmlClassName(DocumentModel::ScxmlDocument *doc) - { - QString name = mangleIdentifier(translationUnit->classnameForDocument.value(doc)); - Q_ASSERT(!name.isEmpty()); - return namespacePrefix + name; + line += QStringLiteral("%1, ").arg(createContainer(l)); } - - /*! - * \internal - * Mangles \a str to be a unique C++ identifier. Characters that are invalid for C++ identifiers - * are replaced by the pattern \c _0x<hex>_ where <hex> is the hexadecimal unicode - * representation of the character. As identifiers with leading underscores followed by either - * another underscore or a capital letter are reserved in C++, we also escape those, by escaping - * the first underscore, using the above method. - * - * We keep track of all identifiers we have used so far and if we find two different names that - * map to the same mangled identifier by the above method, we append underscores to the new one - * until the result is unique. - * - * \note - * Although C++11 allows for non-ascii (unicode) characters to be used in identifiers, - * many compilers forgot to read the spec and do not implement this. Some also do not - * implement C99 identifiers, because that is \e {at the implementation's discretion}. So, - * we are stuck with plain old boring identifiers. - */ - QString mangleIdentifier(const QString &str) const { - auto isNonDigit = [](QChar c) -> bool { - return (c >= QLatin1Char('a') && c <= QLatin1Char('z')) || - (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || - c == QLatin1Char('_'); - }; - - Q_ASSERT(!str.isEmpty()); - - QString mangled; - mangled.reserve(str.size()); - - int i = 0; - if (str.startsWith(QLatin1Char('_')) && str.size() > 1) { - QChar ch = str.at(1); - if (ch == QLatin1Char('_') - || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z'))) { - mangled += QLatin1String("_0x5f_"); - ++i; - } - } - - for (int ei = str.length(); i != ei; ++i) { - auto c = str.at(i).unicode(); - if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) { - mangled += c; - } else { - mangled += QLatin1String("_0x") + QString::number(c, 16) + QLatin1Char('_'); - } + QStringList l; + for (const auto ¶meter : parameters) { + l += QStringLiteral("param(%1, %2, %3)") + .arg(QString::number(parameter.name), + QString::number(parameter.expr), + QString::number(parameter.location)); } - - while (true) { - auto it = m_mangledToOriginal.constFind(mangled); - if (it == m_mangledToOriginal.constEnd()) { - m_mangledToOriginal.insert(mangled, str); - break; - } else if (it.value() == str) { - break; - } - mangled += QStringLiteral("_"); // append underscores until we get a unique name - } - - return mangled; + line += QStringLiteral("%1);").arg(createContainer(l)); } -private: - QString namespacePrefix; - ClassDump &clazz; - TranslationUnit *translationUnit; - mutable QHash<AbstractState *, QHash<NameForm, QString> > m_mangledNames; - mutable QHash<QString, QString> m_mangledToOriginal; - QVector<Node *> m_parents; - QList<QPair<QString, QString>> m_serviceProps; - QSet<QString> m_knownEvents; - QSet<QString> m_signals; - QStringList m_signalNames; - QStringList m_stateNames; - QStringList m_stateFieldNames; - QString m_currentTransitionName; - bool m_bindLate; - bool m_qtMode; - QVector<DocumentModel::DataElement *> m_dataElements; -}; + factories.append(line); + return idx; +} } // anonymous namespace - void CppDumper::dump(TranslationUnit *unit) { Q_ASSERT(unit); @@ -1313,38 +418,73 @@ void CppDumper::dump(TranslationUnit *unit) m_translationUnit = unit; - QStringList classDecls; - QVector<ClassDump> clazzes; - auto docs = m_translationUnit->otherDocuments(); - clazzes.resize(docs.size() + 1); - DumperVisitor(clazzes[0], m_translationUnit).process(unit->mainDocument); + QString namespacePrefix; + if (!m_translationUnit->namespaceName.isEmpty()) { + namespacePrefix = QStringLiteral("::%1").arg(m_translationUnit->namespaceName); + } + + QStringList classNames; + QVector<GeneratedTableData> tables; + QVector<GeneratedTableData::MetaDataInfo> metaDataInfos; + QVector<GeneratedTableData::DataModelInfo> dataModelInfos; + QVector<QStringList> factories; + auto docs = m_translationUnit->allDocuments; + tables.resize(docs.size()); + metaDataInfos.resize(tables.size()); + dataModelInfos.resize(tables.size()); + factories.resize(tables.size()); + auto classnameForDocument = m_translationUnit->classnameForDocument; + for (int i = 0, ei = docs.size(); i != ei; ++i) { auto doc = docs.at(i); - ClassDump &clazz = clazzes[i + 1]; - DumperVisitor(clazz, m_translationUnit).process(doc); - classDecls.append(clazz.className); + auto metaDataInfo = &metaDataInfos[i]; + GeneratedTableData::build(doc, &tables[i], metaDataInfo, &dataModelInfos[i], + [this, &factories, i, &classnameForDocument, &namespacePrefix]( + const QScxmlExecutableContent::InvokeInfo &invokeInfo, + const QVector<QScxmlExecutableContent::StringId> &names, + const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters, + const QSharedPointer<DocumentModel::ScxmlDocument> &content) -> int { + QString className; + if (invokeInfo.expr == QScxmlExecutableContent::NoInstruction) { + className = mangleIdentifier(classnameForDocument.value(content.data())); + } + return createFactoryId(factories[i], className, namespacePrefix, + invokeInfo, names, parameters); + }); + classNames.append(mangleIdentifier(classnameForDocument.value(doc))); } - QString headerName = QFileInfo(unit->outHFileName).fileName(); + const QString headerName = QFileInfo(m_translationUnit->outHFileName).fileName(); const QString headerGuard = headerName.toUpper() .replace(QLatin1Char('.'), QLatin1Char('_')) .replace(QLatin1Char('-'), QLatin1Char('_')); - writeHeaderStart(headerGuard, classDecls); - writeImplStart(clazzes); + const QStringList forwardDecls = classNames.mid(1); + writeHeaderStart(headerGuard, forwardDecls); + writeImplStart(); + + for (int i = 0, ei = tables.size(); i != ei; ++i) { + const GeneratedTableData &table = tables.at(i); + DocumentModel::ScxmlDocument *doc = docs.at(i); + writeClass(classNames.at(i), metaDataInfos.at(i)); + writeImplBody(table, classNames.at(i), doc, factories.at(i), metaDataInfos.at(i)); - foreach (const ClassDump &clazz, clazzes) { - writeClass(clazz); - writeImplBody(clazz); + if (doc->root->dataModel == DocumentModel::Scxml::CppDataModel) { + Replacements r; + r[QStringLiteral("datamodel")] = doc->root->cppDataModelClassName; + generateCppDataModelEvaluators(dataModelInfos.at(i), r); + genTemplate(cpp, QStringLiteral(":/cppdatamodel.t"), r); + } } - classDecls.append(clazzes.at(0).className); - writeHeaderEnd(headerGuard, classDecls); + writeHeaderEnd(headerGuard, classNames); writeImplEnd(); } void CppDumper::writeHeaderStart(const QString &headerGuard, const QStringList &forwardDecls) { - h << doNotEditComment.arg(m_translationUnit->scxmlFileName, QString::number(Q_QSCXMLC_OUTPUT_REVISION), QString::fromLatin1(QT_VERSION_STR)) + h << doNotEditComment.arg(m_translationUnit->scxmlFileName, + QString::number(Q_QSCXMLC_OUTPUT_REVISION), + QString::fromLatin1(QT_VERSION_STR)) << endl; h << QStringLiteral("#ifndef ") << headerGuard << endl @@ -1355,59 +495,18 @@ void CppDumper::writeHeaderStart(const QString &headerGuard, const QStringList & h << l("namespace ") << m_translationUnit->namespaceName << l(" {") << endl << endl; if (!forwardDecls.isEmpty()) { - foreach (const QString &name, forwardDecls) { - h << QStringLiteral("class %1;").arg(name) << endl; - } + for (const QString &forwardDecl : forwardDecls) + h << QStringLiteral("class %1;").arg(forwardDecl) << endl; h << endl; } } -void CppDumper::writeClass(const ClassDump &clazz) +void CppDumper::writeClass(const QString &className, const GeneratedTableData::MetaDataInfo &info) { - h << l("class ") << clazz.className << QStringLiteral(": public QScxmlStateMachine\n{") << endl; - h << QStringLiteral("public:") << endl - << QStringLiteral(" /* qmake ignore Q_OBJECT */") << endl - << QStringLiteral(" Q_OBJECT") << endl; - clazz.properties.write(h, QStringLiteral(" "), QStringLiteral("\n")); - - h << endl - << QStringLiteral("public:") << endl; - h << l(" ") << clazz.className << l("(QObject *parent = 0);") << endl; - h << l(" ~") << clazz.className << "();" << endl; - - if (!clazz.publicMethods.isEmpty()) { - h << endl; - foreach (const Method &m, clazz.publicMethods) { - h << QStringLiteral(" ") << m.decl << QLatin1Char(';') << endl; - } - } - - if (!clazz.protectedMethods.isEmpty()) { - h << endl - << QStringLiteral("protected:") << endl; - foreach (const Method &m, clazz.protectedMethods) { - h << QStringLiteral(" ") << m.decl << QLatin1Char(';') << endl; - } - } - - if (!clazz.signalMethods.isEmpty()) { - h << endl - << QStringLiteral("signals:") << endl; - clazz.signalMethods.write(h, QStringLiteral(" "), QStringLiteral("\n")); - } - - if (!clazz.publicSlotDeclarations.isEmpty()) { - h << endl - << QStringLiteral("public slots:") << endl; - clazz.publicSlotDeclarations.write(h, QStringLiteral(" "), QStringLiteral("\n")); - } - - h << endl - << l("private:") << endl - << l(" struct Data;") << endl - << l(" friend struct Data;") << endl - << l(" struct Data *data;") << endl - << l("};") << endl << endl; + Replacements r; + r[QStringLiteral("classname")] = className; + r[QStringLiteral("properties")] = generatePropertyDecls(info); + genTemplate(h, QStringLiteral(":/decl.t"), r); } void CppDumper::writeHeaderEnd(const QString &headerGuard, const QStringList &metatypeDecls) @@ -1419,129 +518,251 @@ void CppDumper::writeHeaderEnd(const QString &headerGuard, const QStringList &me ns = QStringLiteral("::%1").arg(m_translationUnit->namespaceName); } - foreach (const QString &name, metatypeDecls) { - h << QStringLiteral("Q_DECLARE_METATYPE(%1::%2*);").arg(ns, name) << endl; + for (const QString &name : metatypeDecls) { + h << QStringLiteral("Q_DECLARE_METATYPE(%1::%2*)").arg(ns, name) << endl; } h << endl; h << QStringLiteral("#endif // ") << headerGuard << endl; } -void CppDumper::writeImplStart(const QVector<ClassDump> &allClazzes) +void CppDumper::writeImplStart() { - cpp << doNotEditComment.arg(m_translationUnit->scxmlFileName, QString::number(Q_QSCXMLC_OUTPUT_REVISION), QString::fromLatin1(QT_VERSION_STR)) + cpp << doNotEditComment.arg(m_translationUnit->scxmlFileName, + QString::number(Q_QSCXMLC_OUTPUT_REVISION), + l(QT_VERSION_STR)) << endl; - StringListDumper includes; - foreach (const ClassDump &clazz, allClazzes) { - includes.text += clazz.implIncludes.text; + QStringList includes; + for (DocumentModel::ScxmlDocument *doc : qAsConst(m_translationUnit->allDocuments)) { + switch (doc->root->dataModel) { + case DocumentModel::Scxml::NullDataModel: + includes += l("QScxmlNullDataModel"); + break; + case DocumentModel::Scxml::JSDataModel: + includes += l("QScxmlEcmaScriptDataModel"); + break; + case DocumentModel::Scxml::CppDataModel: + includes += doc->root->cppDataModelHeaderName; + break; + } + } - includes.unique(); + includes.sort(); + includes.removeDuplicates(); QString headerName = QFileInfo(m_translationUnit->outHFileName).fileName(); cpp << l("#include \"") << headerName << l("\"") << endl; cpp << endl - << QStringLiteral("#include <qscxmlqstates.h>") << endl + << QStringLiteral("#include <qscxmlinvokableservice.h>") << endl << QStringLiteral("#include <qscxmltabledata.h>") << endl; - if (!includes.isEmpty()) { - includes.write(cpp, QStringLiteral("#include <"), QStringLiteral(">\n")); - cpp << endl; + for (const QString &inc : qAsConst(includes)) { + cpp << l("#include <") << inc << l(">") << endl; } cpp << endl - << revisionCheck.arg(m_translationUnit->scxmlFileName, QString::number(Q_QSCXMLC_OUTPUT_REVISION), QString::fromLatin1(QT_VERSION_STR)) + << revisionCheck.arg(m_translationUnit->scxmlFileName, + QString::number(Q_QSCXMLC_OUTPUT_REVISION), + QString::fromLatin1(QT_VERSION_STR)) << endl; if (!m_translationUnit->namespaceName.isEmpty()) cpp << l("namespace ") << m_translationUnit->namespaceName << l(" {") << endl << endl; } -void CppDumper::writeImplBody(const ClassDump &clazz) +void CppDumper::writeImplBody(const GeneratedTableData &table, + const QString &className, + DocumentModel::ScxmlDocument *doc, + const QStringList &factory, + const GeneratedTableData::MetaDataInfo &info) +{ + QString dataModelField, dataModelInitialization; + switch (doc->root->dataModel) { + case DocumentModel::Scxml::NullDataModel: + dataModelField = l("QScxmlNullDataModel dataModel;"); + dataModelInitialization = l("stateMachine.setDataModel(&dataModel);"); + break; + case DocumentModel::Scxml::JSDataModel: + dataModelField = l("QScxmlEcmaScriptDataModel dataModel;"); + dataModelInitialization = l("stateMachine.setDataModel(&dataModel);"); + break; + case DocumentModel::Scxml::CppDataModel: + dataModelField = QStringLiteral("// Data model %1 is set from outside.").arg( + doc->root->cppDataModelClassName); + dataModelInitialization = dataModelField; + break; + } + + QString name; + if (table.theName == -1) { + name = QStringLiteral("QString()"); + } else { + name = QStringLiteral("string(%1)").arg(table.theName); + } + + QString serviceFactories; + if (factory.isEmpty()) { + serviceFactories = QStringLiteral(" Q_UNUSED(id);\n Q_UNREACHABLE();"); + } else { + serviceFactories = QStringLiteral(" switch (id) {\n ") + + factory.join(QStringLiteral("\n ")) + + QStringLiteral("\n default: Q_UNREACHABLE();\n }"); + } + + + Replacements r; + r[QStringLiteral("classname")] = className; + r[QStringLiteral("name")] = name; + r[QStringLiteral("initialSetup")] = QString::number(table.initialSetup()); + generateTables(table, r); + r[QStringLiteral("dataModelField")] = dataModelField; + r[QStringLiteral("dataModelInitialization")] = dataModelInitialization; + r[QStringLiteral("theStateMachineTable")] = + GeneratedTableData::toString(table.stateMachineTable()); + r[QStringLiteral("metaObject")] = generateMetaObject(className, info); + r[QStringLiteral("serviceFactories")] = serviceFactories; + genTemplate(cpp, QStringLiteral(":/data.t"), r); +} + +void CppDumper::writeImplEnd() { - cpp << l("struct ") << clazz.className << l("::Data: private QScxmlTableData"); - if (clazz.needsEventFilter) { - cpp << QStringLiteral(", public QScxmlEventFilter"); + if (!m_translationUnit->namespaceName.isEmpty()) { + cpp << endl + << QStringLiteral("} // %1 namespace").arg(m_translationUnit->namespaceName) << endl; } - cpp << l(" {") << endl; - - cpp << QStringLiteral(" Data(%1 &stateMachine)").arg(clazz.className) << endl - << QStringLiteral(" : stateMachine(stateMachine)") << endl; - clazz.constructor.initializer.write(cpp, QStringLiteral(" , "), QStringLiteral("\n")); - cpp << l(" {") << endl; - clazz.constructor.impl.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); - cpp << l(" }") << endl; - - cpp << endl; - cpp << l(" void init() {\n"); - clazz.init.impl.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); - cpp << l(" }") << endl; - cpp << endl; - clazz.dataMethods.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); +} - cpp << endl - << QStringLiteral(" %1 &stateMachine;").arg(clazz.className) << endl; - clazz.classFields.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); +/*! + * \internal + * Mangles \a str to be a unique C++ identifier. Characters that are invalid for C++ identifiers + * are replaced by the pattern \c _0x<hex>_ where <hex> is the hexadecimal unicode + * representation of the character. As identifiers with leading underscores followed by either + * another underscore or a capital letter are reserved in C++, we also escape those, by escaping + * the first underscore, using the above method. + * + * We keep track of all identifiers we have used so far and if we find two different names that + * map to the same mangled identifier by the above method, we append underscores to the new one + * until the result is unique. + * + * \note + * Although C++11 allows for non-ascii (unicode) characters to be used in identifiers, + * many compilers forgot to read the spec and do not implement this. Some also do not + * implement C99 identifiers, because that is \e {at the implementation's discretion}. So, + * we are stuck with plain old boring identifiers. + */ +QString CppDumper::mangleIdentifier(const QString &str) +{ + auto isNonDigit = [](QChar c) -> bool { + return (c >= QLatin1Char('a') && c <= QLatin1Char('z')) || + (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || + c == QLatin1Char('_'); + }; - cpp << l("};") << endl - << endl; - clazz.classMethods.write(cpp, QStringLiteral(""), QStringLiteral("\n")); + Q_ASSERT(!str.isEmpty()); - cpp << clazz.className << l("::") << clazz.className << l("(QObject *parent)") << endl - << QStringLiteral(" : QScxmlStateMachine(parent)") << endl - << QStringLiteral(" , data(new Data(*this))") << endl - << QStringLiteral("{ qRegisterMetaType< %1 * >(); data->init(); }").arg(clazz.className) << endl - << endl; - cpp << clazz.className << l("::~") << clazz.className << l("()") << endl - << l("{ delete data; }") << endl - << endl; - foreach (const Method &m, clazz.publicMethods) { - m.impl.write(cpp, QStringLiteral(""), QStringLiteral("\n"), clazz.className); - cpp << endl; - } - if (!clazz.protectedMethods.isEmpty()) { - cpp << "#define SET_SERVICE_PROP(s, n, fq, sig) \\\n" - " if (id == data->string(s)) { \\\n" - " QScxmlInvokableScxml *machine = service ? dynamic_cast<QScxmlInvokableScxml *>(service) : Q_NULLPTR; \\\n" - " fq *casted = machine ? dynamic_cast<fq*>(machine->stateMachine()) : Q_NULLPTR; \\\n" - " if (data->n != casted) { \\\n" - " data->n = casted; \\\n" - " void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&casted)) }; \\\n" - " QMetaObject::activate(this, &staticMetaObject, sig, _a); \\\n" - " } \\\n" - " return; \\\n" - " }\n" - << endl; - foreach (const Method &m, clazz.protectedMethods) { - m.impl.write(cpp, QStringLiteral(""), QStringLiteral("\n"), clazz.className); - cpp << endl; + QString mangled; + mangled.reserve(str.size()); + + int i = 0; + if (str.startsWith(QLatin1Char('_')) && str.size() > 1) { + QChar ch = str.at(1); + if (ch == QLatin1Char('_') + || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z'))) { + mangled += QLatin1String("_0x5f_"); + ++i; } - cpp << QStringLiteral("#undef SET_SERVICE_PROP") << endl - << endl; } - clazz.publicSlotDefinitions.write(cpp, QStringLiteral("\n"), QStringLiteral("\n")); - cpp << endl; - clazz.tables.write(cpp, QStringLiteral(""), QStringLiteral("\n")); + for (int ei = str.length(); i != ei; ++i) { + auto c = str.at(i).unicode(); + if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) { + mangled += c; + } else { + mangled += QLatin1String("_0x") + QString::number(c, 16) + QLatin1Char('_'); + } + } - if (!clazz.dataModelMethods.isEmpty()) { - bool first = true; - foreach (const Method &m, clazz.dataModelMethods) { - if (first) { - first = false; - } else { - cpp << endl; - } - m.impl.write(cpp, QStringLiteral(""), QStringLiteral("\n")); + while (true) { + auto it = m_mangledToOriginal.constFind(mangled); + if (it == m_mangledToOriginal.constEnd()) { + m_mangledToOriginal.insert(mangled, str); + break; + } else if (it.value() == str) { + break; } + mangled += QStringLiteral("_"); // append underscores until we get a unique name } - cpp << endl << clazz.metaData; + return mangled; } -void CppDumper::writeImplEnd() +QString CppDumper::generatePropertyDecls(const GeneratedTableData::MetaDataInfo &info) { - if (!m_translationUnit->namespaceName.isEmpty()) { - cpp << endl - << QStringLiteral("} // %1 namespace").arg(m_translationUnit->namespaceName) << endl; + QString decls; + + for (const QString &stateName : info.stateNames) { + if (!stateName.isEmpty()) + decls += QString::fromLatin1(" Q_PROPERTY(bool %1)\n").arg(stateName); } + + return decls; +} + +QString CppDumper::generateMetaObject(const QString &className, + const GeneratedTableData::MetaDataInfo &info) +{ + ClassDef classDef; + classDef.classname = className.toUtf8(); + classDef.qualified = classDef.classname; + classDef.superclassList << qMakePair(QByteArray("QScxmlStateMachine"), FunctionDef::Public); + classDef.hasQObject = true; + + // stateNames: + int stateIdx = 0; + for (const QString &stateName : info.stateNames) { + if (stateName.isEmpty()) + continue; + + QByteArray mangledStateName = stateName.toUtf8(); + + FunctionDef signal; + signal.type.name = "void"; + signal.type.rawName = signal.type.name; + signal.normalizedType = signal.type.name; + signal.name = mangledStateName + "Changed"; + signal.access = FunctionDef::Public; + signal.isSignal = true; + signal.implementation = "QMetaObject::activate(_o, &staticMetaObject, %d, _a);"; + + ArgumentDef arg; + arg.type.name = "bool"; + arg.type.rawName = arg.type.name; + arg.normalizedType = arg.type.name; + arg.name = "active"; + arg.typeNameForCast = arg.type.name + "*"; + signal.arguments << arg; + classDef.signalList << signal; + + ++classDef.notifyableProperties; + PropertyDef prop; + prop.name = stateName.toUtf8(); + prop.type = "bool"; + prop.read = "isActive(" + QByteArray::number(stateIdx++) + ")"; + prop.notify = mangledStateName + "Changed"; + prop.notifyId = classDef.signalList.size() - 1; + prop.gspec = PropertyDef::ValueSpec; + prop.scriptable = "true"; + classDef.propertyList << prop; + } + + // sub-statemachines: + QHash<QByteArray, QByteArray> knownQObjectClasses; + knownQObjectClasses.insert(QByteArray("QScxmlStateMachine"), QByteArray()); + + QBuffer buf; + buf.open(QIODevice::WriteOnly); + Generator(&classDef, QList<QByteArray>(), knownQObjectClasses, + QHash<QByteArray, QByteArray>(), buf).generateCode(); + buf.close(); + return QString::fromUtf8(buf.buffer()); } QT_END_NAMESPACE diff --git a/tools/qscxmlc/scxmlcppdumper.h b/tools/qscxmlc/scxmlcppdumper.h index f02bf2f..95fa48a 100644 --- a/tools/qscxmlc/scxmlcppdumper.h +++ b/tools/qscxmlc/scxmlcppdumper.h @@ -31,35 +31,26 @@ #include "qscxmlglobals.h" -#include <QtScxml/private/qscxmlparser_p.h> +#include <QtScxml/private/qscxmlcompiler_p.h> +#include <QtScxml/private/qscxmltabledata_p.h> #include <QTextStream> QT_BEGIN_NAMESPACE -struct ClassDump; - struct TranslationUnit { TranslationUnit() - : useCxx11(true) - , mainDocument(Q_NULLPTR) + : mainDocument(Q_NULLPTR) {} QString scxmlFileName; QString outHFileName, outCppFileName; QString namespaceName; - bool useCxx11; DocumentModel::ScxmlDocument *mainDocument; + QList<DocumentModel::ScxmlDocument *> allDocuments; QHash<DocumentModel::ScxmlDocument *, QString> classnameForDocument; QList<TranslationUnit *> dependencies; - - QList<DocumentModel::ScxmlDocument *> otherDocuments() const - { - auto docs = classnameForDocument.keys(); - docs.removeOne(mainDocument); - return docs; - } }; class CppDumper @@ -74,13 +65,23 @@ public: private: void writeHeaderStart(const QString &headerGuard, const QStringList &forwardDecls); - void writeClass(const ClassDump &clazz); + void writeClass(const QString &className, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); void writeHeaderEnd(const QString &headerGuard, const QStringList &metatypeDecls); - void writeImplStart(const QVector<ClassDump> &allClazzes); - void writeImplBody(const ClassDump &clazz); + void writeImplStart(); + void writeImplBody(const QScxmlInternal::GeneratedTableData &table, + const QString &className, + DocumentModel::ScxmlDocument *doc, + const QStringList &factory, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); void writeImplEnd(); + QString mangleIdentifier(const QString &str); private: + QString generatePropertyDecls(const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateMetaObject(const QString &className, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QTextStream &h; QTextStream &cpp; @@ -88,6 +89,8 @@ private: static QLatin1String l (const char *str) { return QLatin1String(str); } TranslationUnit *m_translationUnit; + + mutable QHash<QString, QString> m_mangledToOriginal; }; QT_END_NAMESPACE diff --git a/tools/qscxmlc/templates.qrc b/tools/qscxmlc/templates.qrc new file mode 100644 index 0000000..6f2ccf6 --- /dev/null +++ b/tools/qscxmlc/templates.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file>data.t</file> + <file>decl.t</file> + <file>cppdatamodel.t</file> + </qresource> +</RCC> |