From 44e30ecea75afc02eda544a250164974c7ba6221 Mon Sep 17 00:00:00 2001 From: Risto Avila Date: Fri, 5 Jun 2015 14:27:02 +0300 Subject: Rename QScxmlLibt to QScxml Change-Id: I772c9b6aa87b5ea61005ebb8f63c0559de8cf348 Reviewed-by: Erik Verbruggen --- src/qml-module/qml-module.pro | 2 +- src/qml-module/state.cpp | 2 +- src/qml-module/statemachine.cpp | 6 +- src/qml-module/statemachine.h | 2 +- src/qscxml/datamodel.cpp | 46 + src/qscxml/datamodel.h | 96 ++ src/qscxml/ecmascriptdatamodel.cpp | 457 +++++++ src/qscxml/ecmascriptdatamodel.h | 63 + src/qscxml/ecmascriptplatformproperties.cpp | 75 ++ src/qscxml/ecmascriptplatformproperties.h | 58 + src/qscxml/executablecontent.cpp | 600 +++++++++ src/qscxml/executablecontent.h | 58 + src/qscxml/executablecontent_p.h | 455 +++++++ src/qscxml/nulldatamodel.cpp | 107 ++ src/qscxml/nulldatamodel.h | 51 + src/qscxml/qscxml.pro | 36 + src/qscxml/scxmlcppdumper.cpp | 886 +++++++++++++ src/qscxml/scxmlcppdumper.h | 66 + src/qscxml/scxmlevent.cpp | 172 +++ src/qscxml/scxmlevent.h | 69 + src/qscxml/scxmlevent_p.h | 105 ++ src/qscxml/scxmlglobals.h | 38 + src/qscxml/scxmlparser.cpp | 1599 ++++++++++++++++++++++++ src/qscxml/scxmlparser.h | 626 ++++++++++ src/qscxml/scxmlstatetable.cpp | 1010 +++++++++++++++ src/qscxml/scxmlstatetable.h | 295 +++++ src/qscxml/scxmlstatetable_p.h | 69 + src/qscxmlcpp/qscxmlcpp.cpp | 4 +- src/qscxmlcpp/qscxmlcpp.pro | 2 +- src/qscxmllib/datamodel.cpp | 46 - src/qscxmllib/datamodel.h | 96 -- src/qscxmllib/ecmascriptdatamodel.cpp | 457 ------- src/qscxmllib/ecmascriptdatamodel.h | 63 - src/qscxmllib/ecmascriptplatformproperties.cpp | 75 -- src/qscxmllib/ecmascriptplatformproperties.h | 58 - src/qscxmllib/executablecontent.cpp | 600 --------- src/qscxmllib/executablecontent.h | 58 - src/qscxmllib/executablecontent_p.h | 455 ------- src/qscxmllib/nulldatamodel.cpp | 107 -- src/qscxmllib/nulldatamodel.h | 51 - src/qscxmllib/qscxmllib.pro | 36 - src/qscxmllib/scxmlcppdumper.cpp | 886 ------------- src/qscxmllib/scxmlcppdumper.h | 66 - src/qscxmllib/scxmlevent.cpp | 172 --- src/qscxmllib/scxmlevent.h | 69 - src/qscxmllib/scxmlevent_p.h | 105 -- src/qscxmllib/scxmlglobals.h | 38 - src/qscxmllib/scxmlparser.cpp | 1599 ------------------------ src/qscxmllib/scxmlparser.h | 626 ---------- src/qscxmllib/scxmlstatetable.cpp | 1010 --------------- src/qscxmllib/scxmlstatetable.h | 295 ----- src/qscxmllib/scxmlstatetable_p.h | 69 - src/src.pro | 8 +- 53 files changed, 7050 insertions(+), 7050 deletions(-) create mode 100644 src/qscxml/datamodel.cpp create mode 100644 src/qscxml/datamodel.h create mode 100644 src/qscxml/ecmascriptdatamodel.cpp create mode 100644 src/qscxml/ecmascriptdatamodel.h create mode 100644 src/qscxml/ecmascriptplatformproperties.cpp create mode 100644 src/qscxml/ecmascriptplatformproperties.h create mode 100644 src/qscxml/executablecontent.cpp create mode 100644 src/qscxml/executablecontent.h create mode 100644 src/qscxml/executablecontent_p.h create mode 100644 src/qscxml/nulldatamodel.cpp create mode 100644 src/qscxml/nulldatamodel.h create mode 100644 src/qscxml/qscxml.pro create mode 100644 src/qscxml/scxmlcppdumper.cpp create mode 100644 src/qscxml/scxmlcppdumper.h create mode 100644 src/qscxml/scxmlevent.cpp create mode 100644 src/qscxml/scxmlevent.h create mode 100644 src/qscxml/scxmlevent_p.h create mode 100644 src/qscxml/scxmlglobals.h create mode 100644 src/qscxml/scxmlparser.cpp create mode 100644 src/qscxml/scxmlparser.h create mode 100644 src/qscxml/scxmlstatetable.cpp create mode 100644 src/qscxml/scxmlstatetable.h create mode 100644 src/qscxml/scxmlstatetable_p.h delete mode 100644 src/qscxmllib/datamodel.cpp delete mode 100644 src/qscxmllib/datamodel.h delete mode 100644 src/qscxmllib/ecmascriptdatamodel.cpp delete mode 100644 src/qscxmllib/ecmascriptdatamodel.h delete mode 100644 src/qscxmllib/ecmascriptplatformproperties.cpp delete mode 100644 src/qscxmllib/ecmascriptplatformproperties.h delete mode 100644 src/qscxmllib/executablecontent.cpp delete mode 100644 src/qscxmllib/executablecontent.h delete mode 100644 src/qscxmllib/executablecontent_p.h delete mode 100644 src/qscxmllib/nulldatamodel.cpp delete mode 100644 src/qscxmllib/nulldatamodel.h delete mode 100644 src/qscxmllib/qscxmllib.pro delete mode 100644 src/qscxmllib/scxmlcppdumper.cpp delete mode 100644 src/qscxmllib/scxmlcppdumper.h delete mode 100644 src/qscxmllib/scxmlevent.cpp delete mode 100644 src/qscxmllib/scxmlevent.h delete mode 100644 src/qscxmllib/scxmlevent_p.h delete mode 100644 src/qscxmllib/scxmlglobals.h delete mode 100644 src/qscxmllib/scxmlparser.cpp delete mode 100644 src/qscxmllib/scxmlparser.h delete mode 100644 src/qscxmllib/scxmlstatetable.cpp delete mode 100644 src/qscxmllib/scxmlstatetable.h delete mode 100644 src/qscxmllib/scxmlstatetable_p.h (limited to 'src') diff --git a/src/qml-module/qml-module.pro b/src/qml-module/qml-module.pro index c401377..4cbf853 100644 --- a/src/qml-module/qml-module.pro +++ b/src/qml-module/qml-module.pro @@ -3,7 +3,7 @@ TARGET = scxmlstatemachine TARGETPATH = Scxml IMPORT_VERSION = 1.0 -QT = qscxmllib qml-private core-private +QT = qscxml qml-private core-private SOURCES = \ $$PWD/plugin.cpp \ diff --git a/src/qml-module/state.cpp b/src/qml-module/state.cpp index 4d41cf4..da240b4 100644 --- a/src/qml-module/state.cpp +++ b/src/qml-module/state.cpp @@ -19,7 +19,7 @@ #include "state.h" #include "statemachine.h" -#include +#include #include State::State(StateMachine *parent) diff --git a/src/qml-module/statemachine.cpp b/src/qml-module/statemachine.cpp index a2534a8..2adec4c 100644 --- a/src/qml-module/statemachine.cpp +++ b/src/qml-module/statemachine.cpp @@ -20,9 +20,9 @@ #include "state.h" #include "statemachine.h" -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/qml-module/statemachine.h b/src/qml-module/statemachine.h index fd5f59c..a192a32 100644 --- a/src/qml-module/statemachine.h +++ b/src/qml-module/statemachine.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include QT_BEGIN_NAMESPACE diff --git a/src/qscxml/datamodel.cpp b/src/qscxml/datamodel.cpp new file mode 100644 index 0000000..7cba99c --- /dev/null +++ b/src/qscxml/datamodel.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "datamodel.h" + +using namespace Scxml; + +DataModel::DataModel(StateTable *table) + : m_table(table) +{ + Q_ASSERT(table); +} + +DataModel::~DataModel() +{ +} + +StateTable *DataModel::table() const +{ + return m_table; +} + +NullDataModel *DataModel::asNullDataModel() +{ + return nullptr; +} + +EcmaScriptDataModel *DataModel::asEcmaScriptDataModel() +{ + return nullptr; +} diff --git a/src/qscxml/datamodel.h b/src/qscxml/datamodel.h new file mode 100644 index 0000000..05a9743 --- /dev/null +++ b/src/qscxml/datamodel.h @@ -0,0 +1,96 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef DATAMODEL_H +#define DATAMODEL_H + +#include "executablecontent.h" + +#include +#include + +namespace Scxml { + +class ScxmlEvent; +class StateTable; + +#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) +#pragma pack(push, 4) // 4 == sizeof(qint32) +#endif +struct EvaluatorInfo { + ExecutableContent::StringId expr; + ExecutableContent::StringId context; +}; + +struct AssignmentInfo { + ExecutableContent::StringId dest; + ExecutableContent::StringId expr; + ExecutableContent::StringId context; +}; + +struct ForeachInfo { + ExecutableContent::StringId array; + ExecutableContent::StringId item; + ExecutableContent::StringId index; + ExecutableContent::StringId context; +}; +#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) +#pragma pack(pop) +#endif + +typedef qint32 EvaluatorId; +enum { NoEvaluator = -1 }; + +class NullDataModel; +class EcmaScriptDataModel; +class SCXML_EXPORT DataModel +{ + Q_DISABLE_COPY(DataModel) + +public: + DataModel(StateTable *table); + virtual ~DataModel(); + + StateTable *table() const; + + virtual void setup() = 0; + + virtual QString evaluateToString(EvaluatorId id, bool *ok) = 0; + virtual bool evaluateToBool(EvaluatorId id, bool *ok) = 0; + virtual QVariant evaluateToVariant(EvaluatorId id, bool *ok) = 0; + virtual void evaluateToVoid(EvaluatorId id, bool *ok) = 0; + virtual void evaluateAssignment(EvaluatorId id, bool *ok) = 0; + virtual bool evaluateForeach(EvaluatorId id, bool *ok, std::function body) = 0; + + virtual void setEvent(const ScxmlEvent &event) = 0; + + virtual QVariant property(const QString &name) const = 0; + virtual bool hasProperty(const QString &name) const = 0; + virtual void setStringProperty(const QString &name, const QString &value, const QString &context, + bool *ok) = 0; + + virtual NullDataModel *asNullDataModel(); + virtual EcmaScriptDataModel *asEcmaScriptDataModel(); + +private: + StateTable *m_table; +}; + +} // namespace Scxml + +#endif // DATAMODEL_H diff --git a/src/qscxml/ecmascriptdatamodel.cpp b/src/qscxml/ecmascriptdatamodel.cpp new file mode 100644 index 0000000..b8728b8 --- /dev/null +++ b/src/qscxml/ecmascriptdatamodel.cpp @@ -0,0 +1,457 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "ecmascriptdatamodel.h" +#include "ecmascriptplatformproperties.h" +#include "executablecontent_p.h" + +#include +#include +#include + +using namespace Scxml; + +typedef std::function ToStringEvaluator; +typedef std::function ToBoolEvaluator; +typedef std::function ToVariantEvaluator; +typedef std::function ToVoidEvaluator; +typedef std::function)> ForeachEvaluator; + +class Scxml::EcmaScriptDataModelPrivate +{ +public: + EcmaScriptDataModelPrivate(EcmaScriptDataModel *q) + : q(q) + {} + + QString evalStr(const QString &expr, const QString &context, bool *ok) + { + QString script = QStringLiteral("(%1).toString()").arg(expr); + QJSValue v = eval(script, context, ok); + if (*ok) + return v.toString(); + else + return QString(); + } + + bool evalBool(const QString &expr, const QString &context, bool *ok) + { + QString script = QStringLiteral("(function(){return !!(%1); })()").arg(expr); + QJSValue v = eval(script, context, ok); + if (*ok) + return v.toBool(); + else + return false; + } + + QJSValue evalJSValue(const QString &expr, const QString &context, bool *ok) + { + Q_ASSERT(engine()); + + QString script = QStringLiteral("(function(){'use strict'; return (\n%1\n); })()").arg(expr); + return eval(script, context, ok); + } + + QJSValue eval(const QString &script, const QString &context, bool *ok) + { + Q_ASSERT(ok); + Q_ASSERT(engine()); + + // FIXME: copy QJSEngine::evaluate and handle the case of v4->catchException() "our way" + + QJSValue v = engine()->evaluate(QStringLiteral("'use strict'; ") + script, QStringLiteral(""), 0); + if (v.isError()) { + *ok = false; + static QByteArray sendid; + table()->submitError(QByteArray("error.execution"), + QStringLiteral("%1 in %2").arg(v.toString(), context), + sendid); + return QJSValue(QJSValue::UndefinedValue); + } else { + *ok = true; + return v; + } + } + + void setupDataModel() + { + Q_ASSERT(engine()); + dataModel = engine()->globalObject(); + + qCDebug(scxmlLog) << "initializing the datamodel"; + setupSystemVariables(); + } + + void setupSystemVariables() + { + setReadonlyProperty(&dataModel, QStringLiteral("_sessionid"), + QStringLiteral("session%1").arg(table()->sessionId())); + + setReadonlyProperty(&dataModel, QStringLiteral("_name"), table()->name()); + + auto scxml = engine()->newObject(); + scxml.setProperty(QStringLiteral("location"), QStringLiteral("#_scxml_%1").arg(table()->sessionId())); + auto ioProcs = engine()->newObject(); + setReadonlyProperty(&ioProcs, QStringLiteral("scxml"), scxml); + setReadonlyProperty(&dataModel, QStringLiteral("_ioprocessors"), ioProcs); + + auto platformVars = PlatformProperties::create(engine(), table()); + dataModel.setProperty(QStringLiteral("_x"), platformVars->jsValue()); + + dataModel.setProperty(QStringLiteral("In"), + engine()->evaluate(QStringLiteral("function(id){return _x.In(id);}"))); + } + + void assignEvent(const ScxmlEvent &event) + { + QJSValue _event = engine()->newObject(); + QJSValue dataValue = eventDataAsJSValue(event); + _event.setProperty(QStringLiteral("data"), dataValue.isUndefined() ? QJSValue(QJSValue::UndefinedValue) + : dataValue); + _event.setProperty(QStringLiteral("invokeid"), event.invokeid().isEmpty() ? QJSValue(QJSValue::UndefinedValue) + : engine()->toScriptValue(QString::fromUtf8(event.invokeid()))); + if (!event.origintype().isEmpty()) + _event.setProperty(QStringLiteral("origintype"), engine()->toScriptValue(event.origintype())); + _event.setProperty(QStringLiteral("origin"), event.origin().isEmpty() ? QJSValue(QJSValue::UndefinedValue) + : engine()->toScriptValue(event.origin()) ); + _event.setProperty(QStringLiteral("sendid"), event.sendid().isEmpty() ? QJSValue(QJSValue::UndefinedValue) + : engine()->toScriptValue(QString::fromUtf8(event.sendid()))); + _event.setProperty(QStringLiteral("type"), engine()->toScriptValue(event.scxmlType())); + _event.setProperty(QStringLiteral("name"), engine()->toScriptValue(QString::fromUtf8(event.name()))); + _event.setProperty(QStringLiteral("raw"), QStringLiteral("unsupported")); // See test178 + // TODO: document this + + setReadonlyProperty(&dataModel, QStringLiteral("_event"), _event); + } + + QJSValue eventDataAsJSValue(const ScxmlEvent &event) const + { + if (event.dataNames().isEmpty()) { + if (event.dataValues().size() == 0) { + return QJSValue(QJSValue::UndefinedValue); + } else if (event.dataValues().size() == 1) { + auto dataValue = event.dataValues().first(); + if (dataValue == QVariant(QMetaType::VoidStar, 0)) { + return QJSValue(QJSValue::NullValue); + } else { + QString data = dataValue.toString(); + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8(), &err); + if (err.error == QJsonParseError::NoError) + return engine()->toScriptValue(doc.toVariant()); + else + return engine()->toScriptValue(data); + } + } else { + Q_UNREACHABLE(); + return QJSValue(QJSValue::UndefinedValue); + } + } else { + auto data = engine()->newObject(); + + for (int i = 0, ei = std::min(event.dataNames().size(), event.dataValues().size()); i != ei; ++i) { + data.setProperty(event.dataNames().at(i), engine()->toScriptValue(event.dataValues().at(i))); + } + + return data; + } + } + + StateTable *table() const + { return q->table(); } + + QJSEngine *engine() const + { + if (jsEngine == nullptr) { + jsEngine = new QJSEngine(table()); + } + + return jsEngine; + } + + void setEngine(QJSEngine *engine) + { jsEngine = engine; } + + QString string(ExecutableContent::StringId id) const + { return table()->tableData()->string(id); } + + StateTable::BindingMethod dataBinding() const + { return table()->dataBinding(); } + + bool hasProperty(const QString &name) const + { return dataModel.hasProperty(name); } + + QJSValue property(const QString &name) const + { return dataModel.property(name); } + + void setProperty(const QString &name, const QJSValue &value, const QString &context, bool *ok) + { + Q_ASSERT(ok); + + QString msg; + switch (setProperty(&dataModel, name, value)) { + case SetPropertySucceeded: + *ok = true; + return; + case SetReadOnlyPropertyFailed: + msg = QStringLiteral("cannot assign to read-only property %1 in %2"); + break; + case SetUnknownPropertyFailed: + msg = QStringLiteral("cannot assign to unknown propety %1 in %2"); + break; + case SetPropertyFailedForAnotherReason: + msg = QStringLiteral("assignment to property %1 failed in %2"); + break; + default: + Q_UNREACHABLE(); + } + + *ok = false; + static QByteArray sendid; + table()->submitError(QByteArray("error.execution"), msg.arg(name, context), sendid); + } + +private: // Uses private API + static void setReadonlyProperty(QJSValue *object, const QString& name, const QJSValue& value) + { + qDebug()<<"setting read-only property"<newString(name)); + uint idx = s->asArrayIndex(); + if (idx < UINT_MAX) { + Q_UNIMPLEMENTED(); + return; + } + + s->makeIdentifier(scope.engine); + QV4::ScopedValue v(scope, QJSValuePrivate::convertedToValue(engine, value)); + o->defineReadonlyProperty(s, v); + if (engine->hasException) + engine->catchException(); + } + + enum SetPropertyResult { + SetPropertySucceeded, + SetReadOnlyPropertyFailed, + SetUnknownPropertyFailed, + SetPropertyFailedForAnotherReason, + }; + + static SetPropertyResult setProperty(QJSValue *object, const QString& name, const QJSValue& value) + { + QV4::ExecutionEngine *engine = QJSValuePrivate::engine(object); + Q_ASSERT(engine); + if (engine->hasException) + return SetPropertyFailedForAnotherReason; + + QV4::Scope scope(engine); + QV4::ScopedObject o(scope, QJSValuePrivate::getValue(object)); + if (o == nullptr) { + return SetPropertyFailedForAnotherReason; + } + + QV4::ScopedString s(scope, engine->newString(name)); + uint idx = s->asArrayIndex(); + if (idx < UINT_MAX) { + Q_UNIMPLEMENTED(); + return SetPropertyFailedForAnotherReason; + } + + QV4::PropertyAttributes attrs = o->query(s); + if (attrs.isWritable() || attrs.isEmpty()) { + QV4::ScopedValue v(scope, QJSValuePrivate::convertedToValue(engine, value)); + o->insertMember(s, v); + if (engine->hasException) { + engine->catchException(); + return SetPropertyFailedForAnotherReason; + } else { + return SetPropertySucceeded; + } + } else { + return SetReadOnlyPropertyFailed; + } + } + +private: + EcmaScriptDataModel *q; + mutable QJSEngine *jsEngine = nullptr; + QJSValue dataModel; +}; + +EcmaScriptDataModel::EcmaScriptDataModel(StateTable *table) + : DataModel(table) + , d(new EcmaScriptDataModelPrivate(this)) +{} + +EcmaScriptDataModel::~EcmaScriptDataModel() +{ + delete d; +} + +void EcmaScriptDataModel::setup() +{ + d->setupDataModel(); + + bool ok; + QJSValue v(QJSValue::UndefinedValue); // See B.2.1, and test456. + int count; + ExecutableContent::StringId *names = table()->tableData()->dataNames(&count); + for (int i = 0; i < count; ++i) + d->setProperty(d->string(names[i]), v, QStringLiteral(""), &ok); + +} + +QString EcmaScriptDataModel::evaluateToString(EvaluatorId id, bool *ok) +{ + const EvaluatorInfo &info = table()->tableData()->evaluatorInfo(id); + + return d->evalStr(d->string(info.expr), d->string(info.context), ok); +} + +bool EcmaScriptDataModel::evaluateToBool(EvaluatorId id, bool *ok) +{ + const EvaluatorInfo &info = table()->tableData()->evaluatorInfo(id); + + return d->evalBool(d->string(info.expr), d->string(info.context), ok); +} + +QVariant EcmaScriptDataModel::evaluateToVariant(EvaluatorId id, bool *ok) +{ + const EvaluatorInfo &info = table()->tableData()->evaluatorInfo(id); + + return d->evalJSValue(d->string(info.expr), d->string(info.context), ok).toVariant(); +} + +void EcmaScriptDataModel::evaluateToVoid(EvaluatorId id, bool *ok) +{ + const EvaluatorInfo &info = table()->tableData()->evaluatorInfo(id); + + d->eval(d->string(info.expr), d->string(info.context), ok); +} + +void EcmaScriptDataModel::evaluateAssignment(EvaluatorId id, bool *ok) +{ + Q_ASSERT(ok); + + const AssignmentInfo &info = table()->tableData()->assignmentInfo(id); + static QByteArray sendid; + + QString dest = d->string(info.dest); + + if (hasProperty(dest)) { + QJSValue v = d->evalJSValue(d->string(info.expr), d->string(info.context), ok); + if (*ok) + d->setProperty(dest, v, d->string(info.context), ok); + } else { + *ok = false; + table()->submitError(QByteArray("error.execution"), + QStringLiteral("%1 in %2 does not exist").arg(dest, d->string(info.context)), + sendid); + } +} + +bool EcmaScriptDataModel::evaluateForeach(EvaluatorId id, bool *ok, std::function body) +{ + Q_ASSERT(ok); + static QByteArray sendid; + const ForeachInfo &info = table()->tableData()->foreachInfo(id); + + QJSValue jsArray = d->property(d->string(info.array)); + if (!jsArray.isArray()) { + table()->submitError("error.execution", QStringLiteral("invalid array '%1' in %2").arg(d->string(info.array), d->string(info.context)), sendid); + *ok = false; + return false; + } + + QString item = d->string(info.item); + if (engine()->evaluate(QStringLiteral("(function(){var %1 = 0})()").arg(item)).isError()) { + table()->submitError("error.execution", QStringLiteral("invalid item '%1' in %2") + .arg(d->string(info.item), d->string(info.context)), sendid); + *ok = false; + return false; + } + + const int length = jsArray.property(QStringLiteral("length")).toInt(); + QString idx = d->string(info.index); + QString context = d->string(info.context); + const bool hasIndex = !idx.isEmpty(); + + for (int currentIndex = 0; currentIndex < length; ++currentIndex) { + QJSValue currentItem = jsArray.property(static_cast(currentIndex)); + d->setProperty(item, currentItem, context, ok); + if (!*ok) + return false; + if (hasIndex) { + d->setProperty(idx, currentIndex, context, ok); + if (!*ok) + return false; + } + if (!body()) + return false; + } + + return true; +} + +void EcmaScriptDataModel::setEvent(const ScxmlEvent &event) +{ + d->assignEvent(event); +} + +QVariant EcmaScriptDataModel::property(const QString &name) const +{ + return d->property(name).toVariant(); +} + +bool EcmaScriptDataModel::hasProperty(const QString &name) const +{ + return d->hasProperty(name); +} + +void EcmaScriptDataModel::setStringProperty(const QString &name, const QString &value, const QString &context, bool *ok) +{ + Q_ASSERT(hasProperty(name)); + d->setProperty(name, QJSValue(value), context, ok); +} + +EcmaScriptDataModel *EcmaScriptDataModel::asEcmaScriptDataModel() +{ + return this; +} + +QJSEngine *EcmaScriptDataModel::engine() const +{ + return d->engine(); +} + +void EcmaScriptDataModel::setEngine(QJSEngine *engine) +{ + d->setEngine(engine); +} diff --git a/src/qscxml/ecmascriptdatamodel.h b/src/qscxml/ecmascriptdatamodel.h new file mode 100644 index 0000000..2dd3412 --- /dev/null +++ b/src/qscxml/ecmascriptdatamodel.h @@ -0,0 +1,63 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef ECMASCRIPTDATAMODEL_H +#define ECMASCRIPTDATAMODEL_H + +#include "datamodel.h" + +QT_BEGIN_NAMESPACE +class QJSEngine; +QT_END_NAMESPACE + +namespace Scxml { + +class EcmaScriptDataModelPrivate; +class SCXML_EXPORT EcmaScriptDataModel: public DataModel +{ +public: + EcmaScriptDataModel(StateTable *table); + ~EcmaScriptDataModel() Q_DECL_OVERRIDE; + + void setup() Q_DECL_OVERRIDE; + + QString evaluateToString(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + bool evaluateToBool(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + QVariant evaluateToVariant(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + void evaluateToVoid(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + void evaluateAssignment(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + bool evaluateForeach(EvaluatorId id, bool *ok, std::function body) Q_DECL_OVERRIDE; + + void setEvent(const ScxmlEvent &event) Q_DECL_OVERRIDE; + + QVariant property(const QString &name) const Q_DECL_OVERRIDE; + bool hasProperty(const QString &name) const Q_DECL_OVERRIDE; + void setStringProperty(const QString &name, const QString &value, const QString &context, bool *ok) Q_DECL_OVERRIDE; + + virtual EcmaScriptDataModel *asEcmaScriptDataModel() Q_DECL_OVERRIDE; + + QJSEngine *engine() const; + void setEngine(QJSEngine *engine); + +private: + EcmaScriptDataModelPrivate *d; +}; + +} // Scxml namespace + +#endif // ECMASCRIPTDATAMODEL_H diff --git a/src/qscxml/ecmascriptplatformproperties.cpp b/src/qscxml/ecmascriptplatformproperties.cpp new file mode 100644 index 0000000..71a7eb2 --- /dev/null +++ b/src/qscxml/ecmascriptplatformproperties.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "ecmascriptplatformproperties.h" +#include "scxmlstatetable.h" + +namespace Scxml { +class PlatformProperties::Data +{ +public: + StateTable *m_table = nullptr; + QJSValue m_jsValue; +}; +} // Scxml namespace + +using namespace Scxml; + +PlatformProperties::PlatformProperties(QObject *parent) + : QObject(parent) + , data(new Data) +{} + +PlatformProperties *PlatformProperties::create(QJSEngine *engine, StateTable *table) +{ + PlatformProperties *pp = new PlatformProperties(engine); + pp->data->m_table = table; + pp->data->m_jsValue = engine->newQObject(pp); + return pp; +} + +QJSEngine *PlatformProperties::engine() const +{ + return qobject_cast(parent()); +} + +StateTable *PlatformProperties::table() const +{ + return data->m_table; +} + +QJSValue PlatformProperties::jsValue() const +{ + return data->m_jsValue; +} + +/// _x.marks() == "the spot" +QString PlatformProperties::marks() const +{ + return QStringLiteral("the spot"); +} + +bool PlatformProperties::In(const QString &stateName) +{ + foreach (QAbstractState *s, table()->configuration()) { + if (s->objectName() == stateName) + return true; + } + + return false; +} diff --git a/src/qscxml/ecmascriptplatformproperties.h b/src/qscxml/ecmascriptplatformproperties.h new file mode 100644 index 0000000..26b89b3 --- /dev/null +++ b/src/qscxml/ecmascriptplatformproperties.h @@ -0,0 +1,58 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef ECMASCRIPTPLATFORMPROPERTIES_H +#define ECMASCRIPTPLATFORMPROPERTIES_H + +#include "scxmlglobals.h" + +#include +#include + +namespace Scxml { + +class StateTable; +class SCXML_EXPORT PlatformProperties: public QObject +{ + Q_OBJECT + + PlatformProperties &operator=(const PlatformProperties &) = delete; + + PlatformProperties(QObject *parent); + + Q_PROPERTY(QString marks READ marks CONSTANT) + +public: + static PlatformProperties *create(QJSEngine *engine, StateTable *table); + + QJSEngine *engine() const; + StateTable *table() const; + QJSValue jsValue() const; + + QString marks() const; + + Q_INVOKABLE bool In(const QString &stateName); + +private: + class Data; + Data *data; +}; + +} // Scxml namespace + +#endif // ECMASCRIPTPLATFORMPROPERTIES_H diff --git a/src/qscxml/executablecontent.cpp b/src/qscxml/executablecontent.cpp new file mode 100644 index 0000000..67db47a --- /dev/null +++ b/src/qscxml/executablecontent.cpp @@ -0,0 +1,600 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "executablecontent_p.h" +#include "scxmlevent_p.h" + +using namespace Scxml; +using namespace Scxml::ExecutableContent; + +static int parseTime(const QString &t, bool *ok = 0) +{ + if (t.isEmpty()) { + if (ok) + *ok = false; + return -1; + } + bool negative = false; + int startPos = 0; + if (t[0] == QLatin1Char('-')) { + negative = true; + ++startPos; + } else if (t[0] == QLatin1Char('+')) { + ++startPos; + } + int pos = startPos; + for (int endPos = t.length(); pos < endPos; ++pos) { + auto c = t[pos]; + if (c < QLatin1Char('0') || c > QLatin1Char('9')) + break; + } + if (pos == startPos) { + if (ok) *ok = false; + return -1; + } + int value = t.midRef(startPos, pos - startPos).toInt(ok); + if (ok && !*ok) return -1; + if (t.length() == pos + 1 && t[pos] == QLatin1Char('s')) { + value *= 1000; + } else if (t.length() != pos + 2 || t[pos] != QLatin1Char('m') || t[pos + 1] != QLatin1Char('s')) { + if (ok) *ok = false; + return -1; + } + return negative ? -value : value; +} + +class ExecutionEngine::Data +{ +public: + Data(StateTable *table) + : table(table) + {} + + bool step(Instructions &ip) + { + auto dataModel = table->dataModel(); + auto tableData = table->tableData(); + + auto instr = reinterpret_cast(ip); + switch (instr->instructionType) { + case Instruction::Sequence: { + qDebug() << "Executing sequence step"; + InstructionSequence *sequence = reinterpret_cast(instr); + ip = sequence->instructions(); + Instructions end = ip + sequence->entryCount; + while (ip < end) { + if (!step(ip)) { + ip = end; + qDebug() << "Finished sequence step UNsuccessfully"; + return false; + } + } + qDebug() << "Finished sequence step successfully"; + return true; + } + + case Instruction::Sequences: { + qDebug() << "Executing sequences step"; + InstructionSequences *sequences = reinterpret_cast(instr); + ip += sequences->size(); + for (int i = 0; i != sequences->sequenceCount; ++i) { + Instructions sequence = sequences->at(i); + step(sequence); + } + qDebug() << "Finished sequences step"; + return true; + } + + case Instruction::Send: { + qDebug() << "Executing send step"; + Send *send = reinterpret_cast(instr); + ip += send->size(); + + QString delay = tableData->string(send->delay); + if (send->delayexpr != NoEvaluator) { + bool ok = false; + delay = table->dataModel()->evaluateToString(send->delayexpr, &ok); + if (!ok) + return false; + } + + ScxmlEvent *event = EventBuilder(table, *send).buildEvent(); + if (!event) + return false; + + if (delay.isEmpty()) { + table->submitEvent(event); + } else { + int msecs = parseTime(delay); + if (msecs >= 0) { + table->submitDelayedEvent(msecs, event); + } else { + qCDebug(scxmlLog) << "failed to parse delay time" << delay; + return false; + } + } + + return true; + } + + case Instruction::JavaScript: { + qDebug() << "Executing javascript step"; + JavaScript *javascript = reinterpret_cast(instr); + ip += javascript->size(); + bool ok = true; + dataModel->evaluateToVoid(javascript->go, &ok); + return ok; + } + + case Instruction::If: { + qDebug() << "Executing if step"; + If *_if = reinterpret_cast(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); + qDebug()<<"Finished if step"; + return res; + } + } + + if (_if->conditions.count < blocks->sequenceCount) { + Instructions block = blocks->at(_if->conditions.count); + return step(block); + } + + return true; + } + + case Instruction::Foreach: { + qDebug() << "Executing foreach step"; + Foreach *foreach = reinterpret_cast(instr); + Instructions loopStart = foreach->blockstart(); + ip += foreach->size(); + bool ok = true; + bool evenMoreOk = dataModel->evaluateForeach(foreach->doIt, &ok, [this,loopStart]()-> bool { + Instructions loop = loopStart; + return step(loop); + }); + return ok && evenMoreOk; + } + + case Instruction::Raise: { + qDebug() << "Executing raise step"; + Raise *raise = reinterpret_cast(instr); + ip += raise->size(); + auto event = tableData->byteArray(raise->event); + table->submitEvent(event, QVariantList(), QStringList(), ScxmlEvent::Internal); + return true; + } + + case Instruction::Log: { + qDebug() << "Executing log step"; + Log *log = reinterpret_cast(instr); + ip += log->size(); + bool ok = true; + QString str = dataModel->evaluateToString(log->expr, &ok); + if (ok) + table->doLog(tableData->string(log->label), str); + return ok; + } + + case Instruction::Cancel: { + qDebug() << "Executing cancel step"; + Cancel *cancel = reinterpret_cast(instr); + ip += cancel->size(); + QByteArray e = tableData->byteArray(cancel->sendid); + bool ok = true; + if (cancel->sendidexpr != NoEvaluator) + e = dataModel->evaluateToString(cancel->sendidexpr, &ok).toUtf8(); + if (ok && !e.isEmpty()) + table->cancelDelayedEvent(e); + return ok; + } + + case Instruction::Invoke: + Q_UNIMPLEMENTED(); + Q_UNREACHABLE(); + return false; + + case Instruction::Assign: { + qDebug() << "Executing assign step"; + Assign *assign = reinterpret_cast(instr); + ip += assign->size(); + bool ok = true; + dataModel->evaluateAssignment(assign->expression, &ok); + return ok; + } + + case Instruction::DoneData: { + qDebug() << "Executing DoneData step"; + DoneData *doneData = reinterpret_cast(instr); + + QString eventName = QStringLiteral("done.state.") + extraData.toString(); + EventBuilder event(table, eventName, doneData); + qDebug() << "submitting event" << eventName; + table->submitEvent(event()); + return true; + } + + default: + Q_UNREACHABLE(); + return false; + } + } + + StateTable *table; + QVariant extraData; +}; + +ExecutionEngine::ExecutionEngine(StateTable *table) + : data(new Data(table)) +{ + Q_ASSERT(table); +} + +ExecutionEngine::~ExecutionEngine() +{ + delete data; +} + +bool ExecutionEngine::execute(ContainerId id, const QVariant &extraData) +{ + Q_ASSERT(data->table); + + if (id == NoInstruction) + return true; + + qint32 *ip = data->table->tableData()->instructions() + id; + data->extraData = extraData; + bool result = data->step(ip); + data->extraData = QVariant(); + return result; +} + +Builder::Builder() +{ + m_activeSequences.reserve(4); +} + +bool Builder::visit(DocumentModel::Send *node) +{ + auto instr = m_instructions.add(Send::calculateExtraSize(node->params.size(), node->namelist.size())); + instr->instructionLocation = createContext(QStringLiteral("send")); + instr->event = addByteArray(node->event.toUtf8()); + 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 = addByteArray(node->id.toUtf8()); + 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(); + instr->event = addByteArray(node->event.toUtf8()); +} + +void Builder::visit(DocumentModel::Log *node) +{ + auto instr = m_instructions.add(); + 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(); + auto ctxt = createContext(QStringLiteral("script"), QStringLiteral("source"), node->content); + instr->go = addEvaluator(node->content, ctxt); +} + +void Builder::visit(DocumentModel::Assign *node) +{ + auto instr = m_instructions.add(); +// qDebug()<<"...:" <location<<"="<expr; + 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(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(); + generate(outSequences, node->blocks); + return false; +} + +bool Builder::visit(DocumentModel::Foreach *node) +{ + auto instr = m_instructions.add(); + 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(); + instr->sendid = addByteArray(node->sendid.toUtf8()); + instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel"), QStringLiteral("sendidexpr"), node->sendidexpr); +} + +bool Builder::visit(DocumentModel::Invoke *) +{ + Q_UNIMPLEMENTED(); + return false; +} + +ContainerId Builder::generate(const DocumentModel::DoneData *node) +{ + auto id = m_instructions.newContainerId(); + DoneData *doneData; + if (node) { + doneData = m_instructions.add(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->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 &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(); + 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(); + generate(outSequences, inSequences); + return id; +} + +void Builder::generate(Array *out, const QVector &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(sequencesOffset); + outSequences->sequenceCount = sequenceCount; + outSequences->entryCount = entryCount; +} + +void Builder::generate(Array *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(); + 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 +// qDebug()<<"starting sequence with depth"<(info.location); + Q_ASSERT(sequence->entryCount == -1); // set in startSequence + sequence->entryCount = info.entryCount; + if (!m_activeSequences.isEmpty()) + m_activeSequences.last().entryCount += info.entryCount; +// qDebug()<<"finished sequence with"<strings = m_stringTable.data(); + td->byteArrays = m_byteArrayTable.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; + return td; +} + + +QString DynamicTableData::string(StringId id) const +{ + return id == NoString ? QString() : strings.at(id); +} + +QByteArray DynamicTableData::byteArray(ByteArrayId id) const +{ + return byteArrays.at(id); +} + +Instructions DynamicTableData::instructions() const +{ + return const_cast(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(theDataNameIds.data()); +} + +QVector DynamicTableData::instructionTable() const +{ + return theInstructions; +} + +QVector DynamicTableData::stringTable() const +{ + return strings; +} + +QVector DynamicTableData::byteArrayTable() const +{ + return byteArrays; +} + +QVector DynamicTableData::evaluators() const +{ + return theEvaluators; +} + +QVector DynamicTableData::assignments() const +{ + return theAssignments; +} + +QVector DynamicTableData::foreaches() const +{ + return theForeaches; +} + +StringIds DynamicTableData::allDataNameIds() const +{ + return theDataNameIds; +} diff --git a/src/qscxml/executablecontent.h b/src/qscxml/executablecontent.h new file mode 100644 index 0000000..1c403aa --- /dev/null +++ b/src/qscxml/executablecontent.h @@ -0,0 +1,58 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef EXECUTABLECONTENT_H +#define EXECUTABLECONTENT_H + +#include "scxmlglobals.h" + +#include +#include +#include + +namespace Scxml { + +class StateTable; + +namespace ExecutableContent { + +typedef int ContainerId; +enum { NoInstruction = -1 }; +typedef qint32 StringId; +typedef QVector StringIds; +enum { NoString = -1 }; +typedef qint32 ByteArrayId; +typedef qint32 *Instructions; + +class SCXML_EXPORT ExecutionEngine +{ +public: + ExecutionEngine(StateTable *table); + ~ExecutionEngine(); + + bool execute(ContainerId ip, const QVariant &extraData = QVariant()); + +private: + class Data; + Data *data; +}; + +} // ExecutableContent namespace +} // namespace Scxml + +#endif // EXECUTABLECONTENT_H diff --git a/src/qscxml/executablecontent_p.h b/src/qscxml/executablecontent_p.h new file mode 100644 index 0000000..7b18fb3 --- /dev/null +++ b/src/qscxml/executablecontent_p.h @@ -0,0 +1,455 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef EXECUTABLECONTENT_P_H +#define EXECUTABLECONTENT_P_H + +#include "datamodel.h" +#include "executablecontent.h" +#include "scxmlparser.h" +#include "scxmlstatetable.h" + +namespace Scxml { + +static inline bool operator<(const EvaluatorInfo &ei1, const EvaluatorInfo &ei2) +{ + if (ei1.expr != ei2.expr) + return ei1.expr < ei2.expr; + else + return ei1.context < ei2.context; +} + +static inline bool operator<(const AssignmentInfo &ai1, const AssignmentInfo &ai2) +{ + if (ai1.dest != ai2.dest) + return ai1.dest < ai2.dest; + else if (ai1.expr != ai2.expr) + return ai1.expr < ai2.expr; + else + return ai1.context < ai2.context; +} + +static inline bool operator<(const ForeachInfo &fi1, const ForeachInfo &fi2) +{ + if (fi1.array != fi2.array) return fi1.array < fi2.array; + if (fi1.item != fi2.item) return fi1.item < fi2.item; + if (fi1.index != fi2.index) return fi1.index < fi2.index; + return fi1.context < fi2.context; +} + +namespace ExecutableContent { + +#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) +#pragma pack(push, 4) // 4 == sizeof(qint32) +#endif + +template +struct Array +{ + qint32 count; + // T[] data; + T *data() { return const_cast(const_data()); } + const T *const_data() const { return reinterpret_cast(reinterpret_cast(this) + sizeof(Array)); } + + const T &at(int pos) { return *(data() + pos); } + int dataSize() const { return count * sizeof(T) / sizeof(qint32); } + int size() const { return sizeof(Array) / sizeof(qint32) + dataSize(); } +}; + +struct SCXML_EXPORT Param +{ + StringId name; + EvaluatorId expr; + StringId location; + + static int calculateSize() { return sizeof(Param) / sizeof(qint32); } +}; + +struct SCXML_EXPORT Instruction +{ + enum InstructionType: qint32 { + Sequence = 1, + Sequences, + Send, + Raise, + Log, + JavaScript, + Assign, + If, + Foreach, + Cancel, + Invoke, + DoneData + } instructionType; +}; + +struct SCXML_EXPORT DoneData: Instruction +{ + StringId location; + StringId contents; + EvaluatorId expr; + Array params; + + static InstructionType kind() { return Instruction::DoneData; } +}; + +struct SCXML_EXPORT InstructionSequence: Instruction +{ + qint32 entryCount; // the amount of qint32's that the instructions take up + // Instruction[] instructions; + + static InstructionType kind() { return Instruction::Sequence; } + Instructions instructions() { return reinterpret_cast(this) + sizeof(InstructionSequence) / sizeof(qint32); } + int size() const { return sizeof(InstructionSequence) / sizeof(qint32) + entryCount; } +}; + +struct SCXML_EXPORT InstructionSequences: Instruction +{ + qint32 sequenceCount; + qint32 entryCount; // the amount of qint32's that the sequences take up + // InstructionSequence[] sequences; + + static InstructionType kind() { return Instruction::Sequences; } + InstructionSequence *sequences() { + return reinterpret_cast(reinterpret_cast(this) + sizeof(InstructionSequences) / sizeof(qint32)); + } + int size() const { return sizeof(InstructionSequences)/sizeof(qint32) + entryCount; } + Instructions at(int pos) { + Instructions seq = reinterpret_cast(sequences()); + while (pos--) { + seq += reinterpret_cast(seq)->size(); + } + return seq; + } +}; + +struct SCXML_EXPORT Send: Instruction +{ + StringId instructionLocation; + ByteArrayId event; + EvaluatorId eventexpr; + StringId type; + EvaluatorId typeexpr; + StringId target; + EvaluatorId targetexpr; + ByteArrayId id; + StringId idLocation; + StringId delay; + EvaluatorId delayexpr; + StringId content; + Array namelist; +// Array params; + + static InstructionType kind() { return Instruction::Send; } + int size() { return sizeof(Send) / sizeof(qint32) + namelist.dataSize() + params()->size(); } + Array *params() { + return reinterpret_cast *>( + reinterpret_cast(this) + sizeof(Send) / sizeof(qint32) + namelist.dataSize()); + } + static int calculateExtraSize(int paramCount, int nameCount) { + return 1 + paramCount * sizeof(Param) / sizeof(qint32) + nameCount * sizeof(StringId) / sizeof(qint32); + } +}; + +struct SCXML_EXPORT Raise: Instruction +{ + ByteArrayId event; + + static InstructionType kind() { return Instruction::Raise; } + int size() const { return sizeof(Raise) / sizeof(qint32); } +}; + +struct SCXML_EXPORT Log: Instruction +{ + StringId label; + EvaluatorId expr; + + static InstructionType kind() { return Instruction::Log; } + int size() const { return sizeof(Log) / sizeof(qint32); } +}; + +struct SCXML_EXPORT JavaScript: Instruction +{ + EvaluatorId go; + + static InstructionType kind() { return Instruction::JavaScript; } + int size() const { return sizeof(JavaScript) / sizeof(qint32); } +}; + +struct SCXML_EXPORT Assign: Instruction +{ + EvaluatorId expression; + + static InstructionType kind() { return Instruction::Assign; } + int size() const { return sizeof(Assign) / sizeof(qint32); } +}; + +struct SCXML_EXPORT If: Instruction +{ + Array conditions; + // InstructionSequences blocks; + InstructionSequences *blocks() { + return reinterpret_cast( + reinterpret_cast(this) + sizeof(If) / sizeof(qint32) + conditions.dataSize()); + } + + static InstructionType kind() { return Instruction::If; } + int size() { return sizeof(If) / sizeof(qint32) + blocks()->size() + conditions.dataSize(); } +}; + +struct SCXML_EXPORT Foreach: Instruction +{ + EvaluatorId doIt; + InstructionSequence block; + + static InstructionType kind() { return Instruction::Foreach; } + int size() const { return sizeof(Foreach) / sizeof(qint32) + block.entryCount; } + Instructions blockstart() { return reinterpret_cast(&block); } +}; + +struct SCXML_EXPORT Cancel: Instruction +{ + ByteArrayId sendid; + EvaluatorId sendidexpr; + + static InstructionType kind() { return Instruction::Cancel; } + int size() const { return sizeof(Cancel) / sizeof(qint32); } +}; + +struct SCXML_EXPORT Invoke: Instruction +{ + StringId type; + StringId typeexpr; + StringId src; + StringId srcexpr; + StringId id; + StringId idLocation; + Array namelist; + qint32 autoforward; + Array params; + InstructionSequence finalize; + + static InstructionType kind() { return Instruction::Invoke; } +}; + +#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) +#pragma pack(pop) +#endif + +class DynamicTableData: public QObject, public TableData +{ + Q_OBJECT + +public: + QString string(ExecutableContent::StringId id) const Q_DECL_OVERRIDE; + QByteArray byteArray(ExecutableContent::ByteArrayId id) const Q_DECL_OVERRIDE; + ExecutableContent::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; + ExecutableContent::StringId *dataNames(int *count) const; + + QVector instructionTable() const; + QVector stringTable() const; + QVector byteArrayTable() const; + QVector evaluators() const; + QVector assignments() const; + QVector foreaches() const; + ExecutableContent::StringIds allDataNameIds() const; + +private: + friend class Builder; + QVector strings; + QVector byteArrays; + QVector theInstructions; + QVector theEvaluators; + QVector theAssignments; + QVector theForeaches; + ExecutableContent::StringIds theDataNameIds; +}; + +class 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; + bool visit(DocumentModel::Invoke *) Q_DECL_OVERRIDE; + +protected: + ContainerId generate(const DocumentModel::DoneData *node); + StringId createContext(const QString &instrName); + void generate(const QVector &dataElements); + ContainerId generate(const DocumentModel::InstructionSequences &inSequences); + void generate(Array *out, const QVector &in); + void generate(InstructionSequences *outSequences, const DocumentModel::InstructionSequences &inSequences); + void generate(Array *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 &cond); + + 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 m_stringTable.add(str); } + + ByteArrayId addByteArray(const QByteArray &ba) + { return m_byteArrayTable.add(ba); } + +private: + template + class Table { + QVector elements; + QMap indexForElement; + + public: + U add(const T &s) { + int pos = indexForElement.value(s, -1); + if (pos == -1) { + pos = elements.size(); + elements.append(s); + indexForElement.insert(s, pos); + } + return pos; + } + + QVector data() { + elements.squeeze(); + return elements; + } + }; + Table m_stringTable; + Table m_byteArrayTable; + + struct SequenceInfo { + int location; + qint32 entryCount; // the amount of qint32's that the instructions take up + }; + QVector m_activeSequences; + + class { + public: + ContainerId newContainerId() const { return m_instr.size(); } + + template + 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(pos); + Q_ASSERT(instr->instructionType == 0); + instr->instructionType = T::kind(); +// qDebug()<<"adding instruction"<(instr) - m_instr.data(); + } + + template + T *at(int offset) + { + return reinterpret_cast(&m_instr[offset]); + } + + QVector data() + { + m_instr.squeeze(); + return m_instr; + } + + void setSequenceInfo(SequenceInfo *info) + { + m_info = info; + } + + private: + QVector m_instr; + SequenceInfo *m_info = nullptr; + } m_instructions; + + + 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); + } + + Table m_evaluators; + Table m_assignments; + Table m_foreaches; + ExecutableContent::StringIds m_dataIds; +}; + +} // ExecutableContent namespace +} // namespace Scxml + +#endif // EXECUTABLECONTENT_P_H diff --git a/src/qscxml/nulldatamodel.cpp b/src/qscxml/nulldatamodel.cpp new file mode 100644 index 0000000..0eb0039 --- /dev/null +++ b/src/qscxml/nulldatamodel.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "nulldatamodel.h" + +using namespace Scxml; + +NullDataModel::NullDataModel(StateTable *table) + : DataModel(table) +{} + +void NullDataModel::setup() +{ +} + +QString NullDataModel::evaluateToString(EvaluatorId id, bool *ok) +{ + Q_UNUSED(id); + Q_UNUSED(ok); + Q_UNREACHABLE(); + return QString(); +} + +bool NullDataModel::evaluateToBool(EvaluatorId id, bool *ok) +{ + Q_UNUSED(id); + Q_UNUSED(ok); + Q_UNREACHABLE(); + return false; +} + +QVariant NullDataModel::evaluateToVariant(EvaluatorId id, bool *ok) +{ + Q_UNUSED(id); + Q_UNUSED(ok); + Q_UNREACHABLE(); + return QVariant(); +} + +void NullDataModel::evaluateToVoid(EvaluatorId id, bool *ok) +{ + Q_UNUSED(id); + Q_UNUSED(ok); + Q_UNREACHABLE(); +} + +void NullDataModel::evaluateAssignment(EvaluatorId id, bool *ok) +{ + Q_UNUSED(id); + Q_UNUSED(ok); + Q_UNREACHABLE(); +} + +bool NullDataModel::evaluateForeach(EvaluatorId id, bool *ok, std::function body) +{ + Q_UNUSED(id); + Q_UNUSED(ok); + Q_UNUSED(body); + Q_UNREACHABLE(); + return false; +} + +void NullDataModel::setEvent(const ScxmlEvent &event) +{ + Q_UNUSED(event); +} + +QVariant NullDataModel::property(const QString &name) const +{ + Q_UNUSED(name); + return QVariant(); +} + +bool NullDataModel::hasProperty(const QString &name) const +{ + Q_UNUSED(name); + return false; +} + +void NullDataModel::setStringProperty(const QString &name, const QString &value, const QString &context, bool *ok) +{ + Q_UNUSED(name); + Q_UNUSED(value); + Q_UNUSED(context); + Q_UNUSED(ok); + Q_UNREACHABLE(); +} + +NullDataModel *NullDataModel::asNullDataModel() +{ + return this; +} diff --git a/src/qscxml/nulldatamodel.h b/src/qscxml/nulldatamodel.h new file mode 100644 index 0000000..ef40d77 --- /dev/null +++ b/src/qscxml/nulldatamodel.h @@ -0,0 +1,51 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef NULLDATAMODEL_H +#define NULLDATAMODEL_H + +#include "datamodel.h" + +namespace Scxml { + +class SCXML_EXPORT NullDataModel: public DataModel +{ +public: + NullDataModel(StateTable *table); + + void setup() Q_DECL_OVERRIDE; + + QString evaluateToString(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + bool evaluateToBool(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + QVariant evaluateToVariant(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + void evaluateToVoid(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + void evaluateAssignment(EvaluatorId id, bool *ok) Q_DECL_OVERRIDE; + bool evaluateForeach(EvaluatorId id, bool *ok, std::function body) Q_DECL_OVERRIDE; + + void setEvent(const ScxmlEvent &event) Q_DECL_OVERRIDE; + + QVariant property(const QString &name) const Q_DECL_OVERRIDE; + bool hasProperty(const QString &name) const Q_DECL_OVERRIDE; + void setStringProperty(const QString &name, const QString &value, const QString &context, bool *ok) Q_DECL_OVERRIDE; + + virtual NullDataModel *asNullDataModel() Q_DECL_OVERRIDE; +}; + +} // Scxml namespace + +#endif // NULLDATAMODEL_H diff --git a/src/qscxml/qscxml.pro b/src/qscxml/qscxml.pro new file mode 100644 index 0000000..a38548c --- /dev/null +++ b/src/qscxml/qscxml.pro @@ -0,0 +1,36 @@ +TARGET = QScxml +MODULE = qscxml + +QT += core core-private qml qml-private + +load(qt_module) + +CONFIG += c++11 +DEFINES += SCXML_LIBRARY \ + QT_NO_CAST_FROM_ASCII + +HEADERS += \ + scxmlparser.h \ + scxmlstatetable.h \ + scxmlglobals.h \ + scxmlstatetable_p.h \ + scxmlcppdumper.h \ + nulldatamodel.h \ + ecmascriptdatamodel.h \ + ecmascriptplatformproperties.h \ + executablecontent.h \ + executablecontent_p.h \ + scxmlevent.h \ + scxmlevent_p.h \ + datamodel.h + +SOURCES += \ + scxmlparser.cpp \ + scxmlstatetable.cpp \ + scxmlcppdumper.cpp \ + nulldatamodel.cpp \ + ecmascriptdatamodel.cpp \ + ecmascriptplatformproperties.cpp \ + executablecontent.cpp \ + scxmlevent.cpp \ + datamodel.cpp diff --git a/src/qscxml/scxmlcppdumper.cpp b/src/qscxml/scxmlcppdumper.cpp new file mode 100644 index 0000000..d72a936 --- /dev/null +++ b/src/qscxml/scxmlcppdumper.cpp @@ -0,0 +1,886 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "executablecontent_p.h" +#include "scxmlcppdumper.h" + +#include + +namespace Scxml { + +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 + { + foreach (const QString &line, text) + out << prefix << line << suffix; + } + + QStringList text; +}; + +struct Method { + QString name; + StringListDumper publicDeclaration; // void f(int i = 0); + StringListDumper implementationHeader; // void f(int i) + StringListDumper initializer; + StringListDumper publicImplementationCode; // d->f(i); + StringListDumper impl; // m_i = ++i; +}; + +struct MainClass { + StringListDumper implIncludes; + QString className; + StringListDumper classFields; + StringListDumper tables; + Method init; + Method initDataModel; + StringListDumper dataMethods; + Method constructor; + Method destructor; + StringListDumper signalMethods; + QList publicMethods; + StringListDumper publicSlotDeclarations; + StringListDumper publicSlotDefinitions; + QList privateMethods; +}; + +namespace { + +QString cEscape(const QString &str) +{ + QString res; + int lastI = 0; + for (int i = 0; i < str.length(); ++i) { + QChar c = str.at(i); + if (c < QLatin1Char(' ') || c == QLatin1Char('\\') || c == QLatin1Char('\"')) { + res.append(str.mid(lastI, i - lastI)); + lastI = i + 1; + if (c == QLatin1Char('\\')) { + res.append(QLatin1String("\\\\")); + } else if (c == QLatin1Char('\"')) { + res.append(QLatin1String("\\\"")); + } else if (c == QLatin1Char('\n')) { + res.append(QLatin1String("\\n")); + } else if (c == QLatin1Char('\r')) { + res.append(QLatin1String("\\r")); + } else { + char buf[6]; + ushort cc = c.unicode(); + buf[0] = '\\'; + buf[1] = 'u'; + for (int i = 0; i < 4; ++i) { + ushort ccc = cc & 0xF; + buf[5 - i] = ccc <= 9 ? '0' + ccc : 'a' + ccc - 10; + cc >>= 4; + } + res.append(QLatin1String(&buf[0], 6)); + } + } + } + if (lastI != 0) { + res.append(str.mid(lastI)); + return res; + } + return str; +} + +static QString toHex(const QString &str) +{ + QString res; + for (int i = 0, ei = str.length(); i != ei; ++i) { + int ch = str.at(i).unicode(); + if (ch < 256) { + res += QStringLiteral("\\x%1").arg(ch, 2, 16, QLatin1Char('0')); + } else { + res += QStringLiteral("\\x%1").arg(ch, 4, 16, QLatin1Char('0')); + } + } + return res; +} + +const char *headerStart = + "#include \n" + "\n"; + +using namespace DocumentModel; + +enum class Evaluator +{ + ToVariant, + ToString, + ToBool, + Assignment, + Foreach, + Script +}; + +class DumperVisitor: public ExecutableContent::Builder +{ + Q_DISABLE_COPY(DumperVisitor) + +public: + DumperVisitor(MainClass &clazz, const QString &mainClassName, const CppDumpOptions &options) + : clazz(clazz) + , m_mainClassName(mainClassName) + , m_options(options) + {} + + void process(ScxmlDocument *doc) + { + doc->root->accept(this); + + addEvents(); + + generateTables(); + } + + ~DumperVisitor() + { + Q_ASSERT(m_parents.isEmpty()); + } + +protected: + using NodeVisitor::visit; + + bool visit(Scxml *node) Q_DECL_OVERRIDE + { + // init: + if (!node->name.isEmpty()) { + clazz.init.impl << QStringLiteral("table.setName(string(%1));").arg(addString(node->name)); + if (m_options.nameQObjects) + clazz.init.impl << QStringLiteral("table.setObjectName(string(%1));").arg(addString(node->name)); + } + QString dmName; + switch (node->dataModel) { + case Scxml::NullDataModel: + dmName = QStringLiteral("Null"); + clazz.implIncludes << QStringLiteral("QScxml/nulldatamodel.h"); + break; + case Scxml::JSDataModel: + dmName = QStringLiteral("EcmaScript"); + clazz.implIncludes << QStringLiteral("QScxml/ecmascriptdatamodel.h"); + break; + default: + Q_UNREACHABLE(); + } + clazz.init.impl << QStringLiteral("table.setDataModel(new Scxml::") + dmName + QStringLiteral("DataModel(&table));"); + + QString binding; + switch (node->binding) { + case DocumentModel::Scxml::EarlyBinding: + binding = QStringLiteral("Early"); + break; + case DocumentModel::Scxml::LateBinding: + binding = QStringLiteral("Late"); + m_bindLate = true; + break; + default: + Q_UNREACHABLE(); + } + clazz.init.impl << QStringLiteral("table.setDataBinding(Scxml::StateTable::%1Binding);").arg(binding); + + clazz.implIncludes << QStringLiteral("QScxml/executablecontent.h"); + clazz.init.impl << QStringLiteral("table.setTableData(this);"); + + foreach (AbstractState *s, node->initialStates) { + clazz.init.impl << QStringLiteral("table.setInitialState(&state_") + mangledName(s) + QStringLiteral(");"); + } + + // 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.init.impl << QStringLiteral("table.setInitialSetup(%1);").arg(startNewSequence()); + generate(m_dataElements); + if (node->script) { + node->script->accept(this); + } + visit(&node->initialSetup); + endSequence(); + } + + m_parents.removeLast(); + return false; + } + + bool visit(State *node) Q_DECL_OVERRIDE + { + auto stateName = QStringLiteral("state_") + mangledName(node); + // Declaration: + if (node->type == State::Final) { + clazz.classFields << QStringLiteral("Scxml::ScxmlFinalState ") + stateName + QLatin1Char(';'); + } else { + clazz.classFields << QStringLiteral("Scxml::ScxmlState ") + stateName + QLatin1Char(';'); + } + + // Initializer: + clazz.constructor.initializer << generateInitializer(node); + + // init: + if (!node->id.isEmpty() && m_options.nameQObjects) { + clazz.init.impl << stateName + QStringLiteral(".setObjectName(string(%1));").arg(addString(node->id)); + } + if (node->initialState) { + clazz.init.impl << stateName + QStringLiteral(".setInitialState(&state_") + mangledName(node->initialState) + QStringLiteral(");"); + } + if (node->type == State::Parallel) { + clazz.init.impl << stateName + QStringLiteral(".setChildMode(QState::ParallelStates);"); + } + + // 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->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); + m_knownEvents.unite(node->events.toSet()); + + // Declaration: + clazz.classFields << QStringLiteral("Scxml::ScxmlTransition ") + tName + QLatin1Char(';'); + + // Initializer: + QString parentName; + auto parent = m_parents.last(); + if (HistoryState *historyState = parent->asHistoryState()) { + parentName = QStringLiteral("state_") + mangledName(historyState) + QStringLiteral("_defaultConfiguration"); + } else { + parentName = parentStateMemberName(); + } + Q_ASSERT(!parentName.isEmpty()); + QString initializer = tName + QStringLiteral("(&") + parentName + QStringLiteral(", {"); + for (int i = 0, ei = node->events.size(); i != ei; ++i) { + if (i == 0) { + initializer += QLatin1Char(' '); + } else { + initializer += QStringLiteral(", "); + } + initializer += qba(node->events.at(i)); + if (i + 1 == ei) { + initializer += QLatin1Char(' '); + } + } + 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); + } + clazz.init.impl << parentName + QStringLiteral(".addTransition(&") + tName + QStringLiteral(");"); + if (node->type == Transition::Internal) { + clazz.init.impl << tName + QStringLiteral(".setTransitionType(QAbstractTransition::InternalTransition);"); + } + QString targets = tName + QStringLiteral(".setTargetStates({"); + for (int i = 0, ei = node->targetStates.size(); i != ei; ++i) { + if (i == 0) { + targets += QLatin1Char(' '); + } else { + targets += QStringLiteral(", "); + } + targets += QStringLiteral("&state_") + mangledName(node->targetStates.at(i)); + if (i + 1 == ei) { + targets += QLatin1Char(' '); + } + } + 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; + } + + bool visit(DocumentModel::HistoryState *node) Q_DECL_OVERRIDE + { + // Includes: + clazz.implIncludes << "QHistoryState"; + + auto stateName = QStringLiteral("state_") + mangledName(node); + // Declaration: + clazz.classFields << QStringLiteral("QHistoryState ") + stateName + QLatin1Char(';'); + + // Initializer: + clazz.constructor.initializer << generateInitializer(node); + + // init: + if (!node->id.isEmpty() && m_options.nameQObjects) { + 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"); + break; + default: + Q_UNREACHABLE(); + } + clazz.init.impl << stateName + QStringLiteral(".setHistoryType(QHistoryState::") + depth + QStringLiteral("History);"); + + // visit the kids: + if (Transition *t = node->defaultConfiguration()) { + // Declaration: + clazz.classFields << QStringLiteral("QState ") + stateName + QStringLiteral("_defaultConfiguration;"); + + // Initializer: + QString init = stateName + QStringLiteral("_defaultConfiguration(&state_"); + if (State *parentState = node->parent->asState()) { + init += mangledName(parentState); + } + clazz.constructor.initializer << init + QLatin1Char(')'); + + // init: + clazz.init.impl << stateName + QStringLiteral(".setDefaultState(&") + + stateName + QStringLiteral("_defaultConfiguration);"); + + // visit the kid: + m_parents.append(node); + t->accept(this); + m_parents.removeLast(); + } + return false; + } + +private: + QString mangledName(AbstractState *state) + { + Q_ASSERT(state); + + QString name = m_mangledNames.value(state); + 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 = CppDumper::mangleId(id); + m_mangledNames.insert(state, name); + return name; + } + + QString transitionName(Transition *t) + { + int idx = 0; + QString parentName; + auto parent = m_parents.last(); + if (State *parentState = parent->asState()) { + parentName = mangledName(parentState); + idx = childIndex(t, parentState->children); + } else if (HistoryState *historyState = parent->asHistoryState()) { + parentName = mangledName(historyState); + } else if (Scxml *scxml = parent->asScxml()) { + parentName = QStringLiteral("table"); + idx = childIndex(t, scxml->children); + } else { + Q_UNREACHABLE(); + } + return QStringLiteral("transition_%1_%2").arg(parentName, QString::number(idx)); + } + + static int childIndex(StateOrTransition *child, const QVector &children) { + int idx = 0; + foreach (StateOrTransition *sot, children) { + if (sot == child) + break; + else + ++idx; + } + return idx; + } + + QString generateInitializer(AbstractState *node) + { + auto stateName = QStringLiteral("state_") + mangledName(node); + QString init = stateName + QStringLiteral("(&"); + if (State *parentState = node->parent->asState()) { + init += QStringLiteral("state_") + mangledName(parentState); + } else { + init += QStringLiteral("table"); + } + init += QLatin1Char(')'); + return init; + } + + void addEvents() + { + QStringList knownEventsList = m_knownEvents.toList(); + std::sort(knownEventsList.begin(), knownEventsList.end()); + foreach (QString event, knownEventsList) { + if (event.startsWith(QStringLiteral("done.")) || event.startsWith(QStringLiteral("qsignal.")) + || event.startsWith(QStringLiteral("qevent."))) { + continue; + } + if (event.contains(QLatin1Char('*'))) + continue; + + clazz.publicSlotDeclarations << QStringLiteral("void event_") + CppDumper::mangleId(event) + QStringLiteral("();"); + clazz.publicSlotDefinitions << QStringLiteral("void ") + m_mainClassName + + QStringLiteral("::event_") + + CppDumper::mangleId(event) + + QStringLiteral("()\n{ submitEvent(data->") + qba(event) + + QStringLiteral("); }"); + } + } + + 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() + { + Node *parent = m_parents.last(); + if (State *s = parent->asState()) + return QStringLiteral("state_") + mangledName(s); + else if (HistoryState *h = parent->asHistoryState()) + return QStringLiteral("state_") + mangledName(h); + else if (parent->asScxml()) + return QStringLiteral("table"); + else + Q_UNIMPLEMENTED(); + return QString(); + } + + static void generateList(StringListDumper &t, std::function next) + { + const int maxLineLength = 80; + QString line; + for (int i = 0; ; ++i) { + QString nr = next(i); + if (nr.isNull()) + break; + + if (i != 0) + line += QLatin1Char(','); + + if (line.length() + nr.length() + 1 > maxLineLength) { + t << line; + line.clear(); + } else if (i != 0) { + line += QLatin1Char(' '); + } + line += nr; + } + if (!line.isEmpty()) + t << line; + } + + void generateTables() + { + StringListDumper &t = clazz.tables; + clazz.classFields << QString(); + QScopedPointer td(tableData()); + + { // instructions + clazz.classFields << QStringLiteral("static qint32 theInstructions[];"); + t << QStringLiteral("qint32 %1::Data::theInstructions[] = {").arg(m_mainClassName); + auto instr = td->instructionTable(); + generateList(t, [&instr](int idx) -> QString { + if (idx < instr.size()) + return QString::number(instr.at(idx)); + else + return QString(); + }); + t << QStringLiteral("};") << QStringLiteral(""); + clazz.dataMethods << QStringLiteral("Scxml::ExecutableContent::Instructions instructions() const Q_DECL_OVERRIDE"); + clazz.dataMethods << QStringLiteral("{ return theInstructions; }"); + clazz.dataMethods << QString(); + } + + { // 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(m_mainClassName); + auto strings = td->stringTable(); + int ucharCount = 0; + generateList(t, [&ucharCount, &strings](int idx) -> QString { + if (idx >= strings.size()) + return QString(); + + auto s = strings.at(idx); + QString str = QStringLiteral("STR_LIT(%1, %2, %3)").arg(QString::number(idx), + QString::number(ucharCount), + QString::number(s.size())); + ucharCount += s.size() + 1; + return str; + }); + t << QStringLiteral("},"); + if (strings.isEmpty()) { + t << QStringLiteral("QT_UNICODE_LITERAL_II(\"\")"); + } else { + foreach (const QString &s, strings) { + t << QStringLiteral("QT_UNICODE_LITERAL_II(\"%1\") // %2").arg(toHex(s) + QStringLiteral("\\x00"), cEscape(s)); + } + } + 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(Scxml::ExecutableContent::StringId id) const Q_DECL_OVERRIDE Q_DECL_FINAL"); + clazz.dataMethods << QStringLiteral("{"); + clazz.dataMethods << QStringLiteral(" Q_ASSERT(id >= Scxml::ExecutableContent::NoString); Q_ASSERT(id < %1);").arg(strings.size()); + clazz.dataMethods << QStringLiteral(" if (id == Scxml::ExecutableContent::NoString) return QString();"); + clazz.dataMethods << QStringLiteral(" return QString({static_cast(strings.data + id)});"); + clazz.dataMethods << QStringLiteral("}"); + clazz.dataMethods << QString(); + } + + { // byte arrays + t << QStringLiteral("#define BA_LIT(idx, ofs, len) \\") + << QStringLiteral(" Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \\") + << QStringLiteral(" qptrdiff(offsetof(ByteArrays, stringdata) + ofs - idx * sizeof(QByteArrayData)) \\") + << QStringLiteral(" )"); + + t << QStringLiteral("%1::Data::ByteArrays %1::Data::byteArrays = {{").arg(m_mainClassName); + auto byteArrays = td->byteArrayTable(); + int charCount = 0; + QString charData; + generateList(t, [&charCount, &charData, &byteArrays](int idx) -> QString { + if (idx >= byteArrays.size()) + return QString(); + + auto ba = byteArrays.at(idx); + QString str = QStringLiteral("BA_LIT(%1, %2, %3)").arg(QString::number(idx), + QString::number(charCount), + QString::number(ba.size())); + charData += toHex(QString::fromUtf8(ba)) + QStringLiteral("\\x00"); + charCount += ba.size() + 1; + return str; + }); + t << QStringLiteral("},"); + if (byteArrays.isEmpty()) { + t << QStringLiteral("\"\""); + } else { + foreach (const QByteArray &ba, byteArrays) { + auto s = QString::fromUtf8(ba); + t << QStringLiteral("\"%1\" // %2").arg(toHex(s) + QStringLiteral("\\x00"), cEscape(s)); + } + } + t << QStringLiteral("};") << QStringLiteral(""); + + clazz.classFields << QStringLiteral("static struct ByteArrays {") + << QStringLiteral(" QByteArrayData data[%1];").arg(byteArrays.size()) + << QStringLiteral(" char stringdata[%1];").arg(charCount + 1) + << QStringLiteral("} byteArrays;"); + + clazz.dataMethods << QStringLiteral("QByteArray byteArray(Scxml::ExecutableContent::ByteArrayId id) const Q_DECL_OVERRIDE Q_DECL_FINAL"); + clazz.dataMethods << QStringLiteral("{"); + clazz.dataMethods << QStringLiteral(" Q_ASSERT(id >= Scxml::ExecutableContent::NoString); Q_ASSERT(id < %1);").arg(byteArrays.size()); + clazz.dataMethods << QStringLiteral(" if (id == Scxml::ExecutableContent::NoString) return QByteArray();"); + clazz.dataMethods << QStringLiteral(" return QByteArray({byteArrays.data + id});"); + clazz.dataMethods << QStringLiteral("}"); + clazz.dataMethods << QString(); + } + + { // dataIds + int count; + auto dataIds = td->dataNames(&count); + clazz.classFields << QStringLiteral("static Scxml::ExecutableContent::StringId dataIds [];"); + t << QStringLiteral("Scxml::ExecutableContent::StringId %1::Data::dataIds[] = {").arg(m_mainClassName); + generateList(t, [&dataIds, count](int idx) -> QString { + if (idx < count) + return QString::number(dataIds[idx]); + else + return QString(); + }); + t << QStringLiteral("};") << QStringLiteral(""); + clazz.dataMethods << QStringLiteral("Scxml::ExecutableContent::StringId *dataNames(int *count) const Q_DECL_OVERRIDE"); + clazz.dataMethods << QStringLiteral("{ *count = %1; return dataIds; }").arg(count); + clazz.dataMethods << QString(); + } + + { // evaluators + auto evaluators = td->evaluators(); + clazz.classFields << QStringLiteral("static Scxml::EvaluatorInfo evaluators[];"); + t << QStringLiteral("Scxml::EvaluatorInfo %1::Data::evaluators[] = {").arg(m_mainClassName); + generateList(t, [&evaluators](int idx) -> QString { + 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("Scxml::EvaluatorInfo evaluatorInfo(Scxml::EvaluatorId evaluatorId) const Q_DECL_OVERRIDE"); + clazz.dataMethods << QStringLiteral("{ Q_ASSERT(evaluatorId >= 0); Q_ASSERT(evaluatorId < %1); return evaluators[evaluatorId]; }").arg(evaluators.size()); + clazz.dataMethods << QString(); + } + + { // assignments + auto assignments = td->assignments(); + clazz.classFields << QStringLiteral("static Scxml::AssignmentInfo assignments[];"); + t << QStringLiteral("Scxml::AssignmentInfo %1::Data::assignments[] = {").arg(m_mainClassName); + generateList(t, [&assignments](int idx) -> QString { + 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("Scxml::AssignmentInfo assignmentInfo(Scxml::EvaluatorId assignmentId) const Q_DECL_OVERRIDE"); + 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 Scxml::ForeachInfo foreaches[];"); + t << QStringLiteral("Scxml::ForeachInfo %1::Data::foreaches[] = {").arg(m_mainClassName); + generateList(t, [&foreaches](int idx) -> QString { + 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("Scxml::ForeachInfo foreachInfo(Scxml::EvaluatorId foreachId) const Q_DECL_OVERRIDE"); + clazz.dataMethods << QStringLiteral("{ Q_ASSERT(foreachId >= 0); Q_ASSERT(foreachId < %1); return foreaches[foreachId]; }").arg(foreaches.size()); + } + } + + QString qba(const QString &bytes) + { +// QString str = QString::fromLatin1("QByteArray::fromRawData(\""); +// auto esc = cEscape(bytes); +// str += esc + QLatin1String("\", ") + QString::number(esc.length()) + QLatin1String(")"); +// return str; + return QStringLiteral("byteArray(%1)").arg(addByteArray(bytes.toUtf8())); + } + +private: + MainClass &clazz; + const QString &m_mainClassName; + const CppDumpOptions &m_options; + QHash m_mangledNames; + QVector m_parents; + QSet m_knownEvents; + QString m_currentTransitionName; + bool m_bindLate = false; + QVector m_dataElements; +}; +} // anonymous namespace + + +void CppDumper::dump(DocumentModel::ScxmlDocument *doc) +{ + m_doc = doc; + mainClassName = options.classname; + if (mainClassName.isEmpty()) { + mainClassName = doc->root->qtClassname; + } + if (mainClassName.isEmpty()) { + mainClassName = mangleId(doc->root->name); + if (!mainClassName.isEmpty()) + mainClassName.append(QLatin1Char('_')); + mainClassName.append(l("StateMachine")); + } + + MainClass clazz; + DumperVisitor(clazz, mainClassName, options).process(doc); + + // Generate the .h file: + const QString headerGuard = headerName.toUpper().replace(QLatin1Char('.'), QLatin1Char('_')); + h << QStringLiteral("#ifndef ") << headerGuard << endl + << QStringLiteral("#define ") << headerGuard << endl + << endl; + h << l(headerStart); + if (!options.namespaceName.isEmpty()) + h << l("namespace ") << options.namespaceName << l(" {") << endl << endl; + h << l("class ") << mainClassName << l(" : public Scxml::StateTable\n{") << endl; + h << QLatin1String(" Q_OBJECT\n\n"); + h << QLatin1String("public:\n"); + h << l(" ") << mainClassName << l("(QObject *parent = 0);") << endl; + h << l(" ~") << mainClassName << "();" << endl; + + if (!clazz.publicSlotDeclarations.isEmpty()) { + h << "\npublic slots:\n"; + clazz.publicSlotDeclarations.write(h, QStringLiteral(" "), QStringLiteral("\n")); + } + + h << endl + << l("private:") << endl + << l(" struct Data;") << endl + << l(" friend Data;") << endl + << l(" struct Data *data;") << endl + << l("};") << endl; + + if (!options.namespaceName.isEmpty()) + h << endl << l("} // namespace ") << options.namespaceName << endl; + h << endl + << QStringLiteral("#endif // ") << headerGuard << endl; + + // Generate the .cpp file: + cpp << l("#include \"") << headerName << l("\"") << endl + << endl; + if (!clazz.implIncludes.isEmpty()) { + clazz.implIncludes.write(cpp, QStringLiteral("#include <"), QStringLiteral(">\n")); + cpp << endl; + } + if (!options.namespaceName.isEmpty()) + cpp << l("namespace ") << options.namespaceName << l(" {") << endl << endl; + + cpp << l("struct ") << mainClassName << l("::Data: private Scxml::TableData {") << endl; + + cpp << QStringLiteral(" Data(%1 &table)\n : table(table)").arg(mainClassName) << endl; + clazz.constructor.initializer.write(cpp, QStringLiteral(" , "), QStringLiteral("\n")); + cpp << l(" { init(); }") << 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 &table;").arg(mainClassName) << endl; + clazz.classFields.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); + + cpp << l("};") << endl + << endl; + cpp << mainClassName << l("::") << mainClassName << l("(QObject *parent)") << endl + << l(" : Scxml::StateTable(parent)") << endl + << l(" , data(new Data(*this))") << endl + << l("{}") << endl + << endl; + cpp << mainClassName << l("::~") << mainClassName << l("()") << endl + << l("{ delete data; }") << endl + << endl; + clazz.publicSlotDefinitions.write(cpp, QStringLiteral("\n"), QStringLiteral("\n")); + cpp << endl; + + clazz.tables.write(cpp, QStringLiteral(""), QStringLiteral("\n")); + + if (!options.namespaceName.isEmpty()) + cpp << l("} // namespace ") << options.namespaceName << endl; +} + +QString CppDumper::mangleId(const QString &id) +{ + QString mangled(id); + mangled = mangled.replace(QLatin1Char('_'), QLatin1String("__")); + mangled = mangled.replace(QLatin1Char(':'), QLatin1String("_colon_")); + mangled = mangled.replace(QLatin1Char('-'), QLatin1String("_dash_")); + mangled = mangled.replace(QLatin1Char('@'), QLatin1String("_at_")); + mangled = mangled.replace(QLatin1Char('.'), QLatin1String("_dot_")); + return mangled; +} + +} // namespace Scxml diff --git a/src/qscxml/scxmlcppdumper.h b/src/qscxml/scxmlcppdumper.h new file mode 100644 index 0000000..f215b28 --- /dev/null +++ b/src/qscxml/scxmlcppdumper.h @@ -0,0 +1,66 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef CPPDUMPER_H +#define CPPDUMPER_H + +#include "scxmlglobals.h" +#include "scxmlparser.h" +#include "scxmlstatetable.h" + +#include + +namespace Scxml { + +struct MainClass; + +struct SCXML_EXPORT CppDumpOptions +{ + CppDumpOptions() : usePrivateApi(false), nameQObjects(false) { } + QString classname; + QString namespaceName; + bool usePrivateApi; + bool nameQObjects; +}; + +class SCXML_EXPORT CppDumper +{ +public: + CppDumper(QTextStream &headerStream, QTextStream &cppStream, const QString &headerName, const CppDumpOptions &options) + : h(headerStream), cpp(cppStream), headerName(headerName), options(options) + {} + + void dump(DocumentModel::ScxmlDocument *doc); + + static QString mangleId(const QString &id); + +private: + QTextStream &h; + QTextStream &cpp; + QString headerName; + + static QByteArray b(const char *str) { return QByteArray(str); } + static QLatin1String l (const char *str) { return QLatin1String(str); } + + DocumentModel::ScxmlDocument *m_doc = nullptr; + QString mainClassName; + CppDumpOptions options; +}; + +} // namespace Scxml +#endif // CPPDUMPER_H diff --git a/src/qscxml/scxmlevent.cpp b/src/qscxml/scxmlevent.cpp new file mode 100644 index 0000000..2034e63 --- /dev/null +++ b/src/qscxml/scxmlevent.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "executablecontent_p.h" +#include "scxmlevent_p.h" + +using namespace Scxml; + +static bool evaluate(const ExecutableContent::Param ¶m, StateTable *table, QVariantList &dataValues, QStringList &dataNames) +{ + auto dataModel = table->dataModel(); + auto tableData = table->tableData(); + if (param.expr != NoEvaluator) { + bool success = false; + auto v = dataModel->evaluateToVariant(param.expr, &success); + dataValues.append(v); + dataNames.append(tableData->string(param.name)); + return success; + } + + QString loc; + if (param.location != ExecutableContent::NoString) { + loc = tableData->string(param.location); + } + + if (loc.isEmpty()) { + return false; + } + + if (dataModel->hasProperty(loc)) { + dataValues.append(dataModel->property(loc)); + dataNames.append(tableData->string(param.name)); + return true; + } else { + table->submitError(QByteArray("error.execution"), + QStringLiteral("Error in : %1 is not a valid location") + .arg(loc), + /*sendid =*/ QByteArray()); + return false; + } +} + +static bool evaluate(const ExecutableContent::Array *params, StateTable *table, QVariantList &dataValues, QStringList &dataNames) +{ + if (!params) + return true; + + auto paramPtr = params->const_data(); + for (qint32 i = 0; i != params->count; ++i, ++paramPtr) { + if (!evaluate(*paramPtr, table, dataValues, dataNames)) + return false; + } + + return true; +} + +QAtomicInt EventBuilder::idCounter = QAtomicInt(0); + +ScxmlEvent *EventBuilder::buildEvent() +{ + auto dataModel = table ? table->dataModel() : nullptr; + auto tableData = table ? table->tableData() : nullptr; + + QByteArray eventName = event; + bool ok = true; + if (eventexpr != NoEvaluator) { + eventName = dataModel->evaluateToString(eventexpr, &ok).toUtf8(); + ok = true; // ignore failure. + } + + QVariantList dataValues; + QStringList dataNames; + if ((!params || params->count == 0) && (!namelist || namelist->count == 0)) { + QVariant data; + if (contentExpr != NoEvaluator) { + data = dataModel->evaluateToString(contentExpr, &ok); + } else { + data = contents; + } + if (ok) { + dataValues.append(data); + } else { + // expr evaluation failure results in the data property of the event being set to null. See e.g. test528. + dataValues = { QVariant(QMetaType::VoidStar, 0) }; + } + } else { + if (evaluate(params, table, dataValues, dataNames)) { + if (namelist) { + for (qint32 i = 0; i < namelist->count; ++i) { + QString name = tableData->string(namelist->const_data()[i]); + dataNames << name; + dataValues << dataModel->property(name); + } + } + } else { + // If the evaluation of the tags fails, set _event.data to an empty string. + // See test343. + dataValues = { QVariant(QMetaType::VoidStar, 0) }; + dataNames.clear(); + } + } + + QByteArray sendid = id; + if (!idLocation.isEmpty()) { + sendid = generateId(); + table->dataModel()->setStringProperty(idLocation, QString::fromUtf8(sendid), tableData->string(instructionLocation), &ok); + if (!ok) + return nullptr; + } + + QString origin = target; + if (targetexpr != NoEvaluator) { + origin = dataModel->evaluateToString(targetexpr, &ok); + if (!ok) + return nullptr; + } + if (origin.isEmpty()) { + if (eventType == ScxmlEvent::External) { + origin = QStringLiteral("#_internal"); + } + } else if (!table->isLegalTarget(origin)) { + // [6.2.4] and test194. + table->submitError(QByteArray("error.execution"), + QStringLiteral("Error in %1: %2 is not a legal target") + .arg(tableData->string(instructionLocation), origin), + sendid); + return nullptr; + } else if (!table->isDispatchableTarget(origin)) { + // [6.2.4] and test521. + table->submitError(QByteArray("error.communication"), + QStringLiteral("Error in %1: cannot dispatch to target '%2'") + .arg(tableData->string(instructionLocation), origin), + sendid); + return nullptr; + } + + QString origintype = type; + if (origintype.isEmpty()) { + // [6.2.5] and test198 + origintype = QStringLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor"); + } + if (typeexpr != NoEvaluator) { + origintype = dataModel->evaluateToString(typeexpr, &ok); + if (!ok) + return nullptr; + } + if (!origintype.isEmpty() && origintype != QStringLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor")) { + // [6.2.5] and test199 + table->submitError(QByteArray("error.execution"), + QStringLiteral("Error in %1: %2 is not a valid type") + .arg(tableData->string(instructionLocation), origintype), + sendid); + return nullptr; + } + + return new ScxmlEvent(eventName, eventType, dataValues, dataNames, sendid, origin, origintype); +} diff --git a/src/qscxml/scxmlevent.h b/src/qscxml/scxmlevent.h new file mode 100644 index 0000000..ca8fd82 --- /dev/null +++ b/src/qscxml/scxmlevent.h @@ -0,0 +1,69 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef SCXMLEVENT_H +#define SCXMLEVENT_H + +#include "scxmlglobals.h" + +#include +#include +#include + +namespace Scxml { + +class SCXML_EXPORT ScxmlEvent: public QEvent { +public: + static QEvent::Type scxmlEventType; + enum EventType { Platform, Internal, External }; + + ScxmlEvent(const QByteArray &name = QByteArray(), EventType eventType = External, + const QVariantList &dataValues = QVariantList(), const QStringList &dataNames = QStringList(), + const QByteArray &sendid = QByteArray (), + const QString &origin = QString (), const QString &origintype = QString (), + const QByteArray &invokeid = QByteArray()); + + QByteArray name() const { return m_name; } + EventType eventType() const { return m_type; } + QString scxmlType() const; + QByteArray sendid() const { return m_sendid; } + QString origin() const { return m_origin; } + QString origintype() const { return m_origintype; } + QByteArray invokeid() const { return m_invokeid; } + QVariantList dataValues() const { return m_dataValues; } + QStringList dataNames() const { return m_dataNames; } + void reset(const QByteArray &name, EventType eventType = External, + QVariantList dataValues = QVariantList(), const QByteArray &sendid = QByteArray(), + const QString &origin = QString(), const QString &origintype = QString(), + const QByteArray &invokeid = QByteArray()); + void clear(); + +private: + QByteArray m_name; + EventType m_type; + QVariantList m_dataValues; // extra data + QStringList m_dataNames; // extra data + QByteArray m_sendid; // if set, or id of if failure + QString m_origin; // uri to answer by setting the target of send, empty for internal and platform events + QString m_origintype; // type to answer by setting the type of send, empty for internal and platform events + QByteArray m_invokeid; // id of the invocation that triggered the child process if this was invoked +}; + +} // namespace Scxml + +#endif // SCXMLEVENT_H diff --git a/src/qscxml/scxmlevent_p.h b/src/qscxml/scxmlevent_p.h new file mode 100644 index 0000000..128a80a --- /dev/null +++ b/src/qscxml/scxmlevent_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef SCXMLEVENT_P_H +#define SCXMLEVENT_P_H + +#include "scxmlevent.h" +#include "scxmlstatetable.h" +#include "executablecontent_p.h" + +#include + +namespace Scxml { + +class EventBuilder +{ + StateTable* table = nullptr; + ExecutableContent::StringId instructionLocation; + QByteArray event; + EvaluatorId eventexpr = NoEvaluator; + QString contents; + EvaluatorId contentExpr = NoEvaluator; + const ExecutableContent::Array *params = nullptr; + ScxmlEvent::EventType eventType = ScxmlEvent::External; + QByteArray id; + QString idLocation; + QString target; + EvaluatorId targetexpr = NoEvaluator; + QString type; + EvaluatorId typeexpr = NoEvaluator; + const ExecutableContent::Array *namelist = nullptr; + + static QAtomicInt idCounter; + QByteArray generateId() const + { + QByteArray id = QByteArray::number(++idCounter); + id.prepend("id-"); + return id; + } + + EventBuilder() + {} + +public: + EventBuilder(StateTable *table, const QString &eventName, const ExecutableContent::DoneData *doneData) + : table(table) + { + Q_ASSERT(doneData); + instructionLocation = doneData->location; + event = eventName.toUtf8(); + contents = table->tableData()->string(doneData->contents); + contentExpr = doneData->expr; + params = &doneData->params; + } + + EventBuilder(StateTable *table, ExecutableContent::Send &send) + : table(table) + , instructionLocation(send.instructionLocation) + , event(table->tableData()->byteArray(send.event)) + , eventexpr(send.eventexpr) + , contents(table->tableData()->string(send.content)) + , params(send.params()) + , id(table->tableData()->byteArray(send.id)) + , idLocation(table->tableData()->string(send.idLocation)) + , target(table->tableData()->string(send.target)) + , targetexpr(send.targetexpr) + , type(table->tableData()->string(send.type)) + , typeexpr(send.typeexpr) + , namelist(&send.namelist) + {} + + ScxmlEvent *operator()() { return buildEvent(); } + + ScxmlEvent *buildEvent(); + + static ScxmlEvent *errorEvent(const QByteArray &name, const QByteArray &sendid) + { + EventBuilder event; + event.event = name; + event.eventType = ScxmlEvent::Platform; // Errors are platform events. See e.g. test331. + // _event.data == null, see test528 + event.id = sendid; + return event(); + } +}; + +} // Scxml namespace + +#endif // SCXMLEVENT_P_H + diff --git a/src/qscxml/scxmlglobals.h b/src/qscxml/scxmlglobals.h new file mode 100644 index 0000000..b60520c --- /dev/null +++ b/src/qscxml/scxmlglobals.h @@ -0,0 +1,38 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ +#ifndef SCXMLGLOBALS_H +#define SCXMLGLOBALS_H +#include + +#ifndef SCXML_EXPORT +# ifndef QT_STATIC +# ifdef SCXML_LIBRARY +# define SCXML_EXPORT Q_DECL_EXPORT +# else +# define SCXML_EXPORT Q_DECL_IMPORT +# endif +# else +# define SCXML_EXPORT +# endif +#endif + +#ifdef QT_DEBUG +#define SCXML_DEBUG +#endif +#endif // SCXMLGLOBALS_H + diff --git a/src/qscxml/scxmlparser.cpp b/src/qscxml/scxmlparser.cpp new file mode 100644 index 0000000..50ac13f --- /dev/null +++ b/src/qscxml/scxmlparser.cpp @@ -0,0 +1,1599 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "scxmlparser.h" +#include "executablecontent_p.h" +#include "nulldatamodel.h" +#include "ecmascriptdatamodel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Scxml { + +static QString scxmlNamespace = QStringLiteral("http://www.w3.org/2005/07/scxml"); +static QString qtScxmlNamespace = QStringLiteral("http://theqtcompany.com/scxml/2015/06/"); + +class ScxmlVerifier: public DocumentModel::NodeVisitor +{ +public: + ScxmlVerifier(std::function errorHandler) + : m_errorHandler(errorHandler) + {} + + bool verify(DocumentModel::ScxmlDocument *doc) + { + if (doc->isVerified) + return true; + + doc->isVerified = true; + m_doc = doc; + foreach (DocumentModel::AbstractState *state, 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!"); +#endif // QT_NO_DEBUG + } else { + m_stateById[state->id] = state; + } + } + + doc->root->accept(this); + return !m_hasErrors; + } + +private: + bool visit(DocumentModel::Scxml *scxml) Q_DECL_OVERRIDE + { + Q_ASSERT(scxml->initialStates.isEmpty()); + + if (scxml->initial.isEmpty()) { + if (auto firstChild = firstAbstractState(scxml)) { + scxml->initialStates.append(firstChild); + } + } else { + foreach (const QString &initial, scxml->initial) { + if (DocumentModel::AbstractState *s = m_stateById.value(initial)) + scxml->initialStates.append(s); + else + error(scxml->xmlLocation, QStringLiteral("initial state '%1' not found for element").arg(initial)); + } + } + + m_parentNodes.append(scxml); + + return true; + } + + void endVisit(DocumentModel::Scxml *) Q_DECL_OVERRIDE + { + m_parentNodes.removeLast(); + } + + bool visit(DocumentModel::State *state) Q_DECL_OVERRIDE + { + Q_ASSERT(state->initialState == nullptr); + + if (state->initial.isEmpty()) { + state->initialState = firstAbstractState(state); + } else { + Q_ASSERT(state->type == DocumentModel::State::Normal); + if (DocumentModel::AbstractState *s = m_stateById.value(state->initial)) { + state->initialState = s; + } else { + error(state->xmlLocation, QStringLiteral("undefined initial state '%1' for state '%2'").arg(state->initial, state->id)); + } + } + + switch (state->type) { + case DocumentModel::State::Normal: + 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() == nullptr) { + error(state->xmlLocation, QStringLiteral("initial states can only occur in a state")); + } + break; + case DocumentModel::State::Final: + break; + default: + Q_UNREACHABLE(); + } + + m_parentNodes.append(state); + return true; + } + + void endVisit(DocumentModel::State *) Q_DECL_OVERRIDE + { + m_parentNodes.removeLast(); + } + + bool visit(DocumentModel::Transition *transition) Q_DECL_OVERRIDE + { + Q_ASSERT(transition->targetStates.isEmpty()); + + if (int size = transition->targets.size()) + transition->targetStates.reserve(size); + foreach (const QString &target, transition->targets) { + if (DocumentModel::AbstractState *s = m_stateById.value(target)) { + if (transition->targetStates.contains(s)) { + error(transition->xmlLocation, QStringLiteral("duplicate target '%1'").arg(target)); + } else { + transition->targetStates.append(s); + } + } else if (!target.isEmpty()) { + error(transition->xmlLocation, QStringLiteral("unknown state '%1' in target").arg(target)); + } + } + + m_parentNodes.append(transition); + return true; + } + + void endVisit(DocumentModel::Transition *) Q_DECL_OVERRIDE + { + m_parentNodes.removeLast(); + } + + bool visit(DocumentModel::HistoryState *state) Q_DECL_OVERRIDE + { + bool seenTransition = false; + foreach (DocumentModel::StateOrTransition *sot, state->children) { + if (DocumentModel::State *s = sot->asState()) { + error(s->xmlLocation, QStringLiteral("history state cannot have substates")); + } else if (DocumentModel::Transition *t = sot->asTransition()) { + if (seenTransition) { + error(t->xmlLocation, QStringLiteral("history state can only have one transition")); + } else { + seenTransition = true; + m_parentNodes.append(state); + t->accept(this); + m_parentNodes.removeLast(); + } + } + } + + return false; + } + + bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE + { + checkExpr(node->xmlLocation, QStringLiteral("send"), QStringLiteral("eventexpr"), node->eventexpr); + return true; + } + + void visit(DocumentModel::Cancel *node) Q_DECL_OVERRIDE + { + checkExpr(node->xmlLocation, QStringLiteral("cancel"), QStringLiteral("sendidexpr"), node->sendidexpr); + } + + bool visit(DocumentModel::DoneData *node) Q_DECL_OVERRIDE + { + checkExpr(node->xmlLocation, QStringLiteral("donedata"), QStringLiteral("expr"), node->expr); + return false; + } + +private: + 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 nullptr; + } + + static DocumentModel::AbstractState *firstAbstractState(DocumentModel::StateContainer *container) + { + QVector children; + if (auto state = container->asState()) + children = state->children; + else if (auto scxml = container->asScxml()) + children = 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 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) + { + m_hasErrors = true; + if (m_errorHandler) + m_errorHandler(location, message); + } + + DocumentModel::Node *parentState() const + { + for (int i = m_parentNodes.size() - 1; i >= 0; --i) { + if (DocumentModel::State *s = m_parentNodes.at(i)->asState()) + return s; + } + + return nullptr; + } + +private: + std::function m_errorHandler; + DocumentModel::ScxmlDocument *m_doc; + bool m_hasErrors = false; + QHash m_stateById; + QVector m_parentNodes; +}; + +class StateTableBuilder: public ExecutableContent::Builder +{ + StateTable *m_table = nullptr; + +public: + StateTable *build(DocumentModel::ScxmlDocument *doc) + { + m_table = nullptr; + m_parents.reserve(32); + m_allTransitions.reserve(doc->allTransitions.size()); + m_docStatesToQStates.reserve(doc->allStates.size()); + + doc->root->accept(this); + wireTransitions(); + applyInitialStates(); + + ExecutableContent::DynamicTableData *td = tableData(); + td->setParent(m_table); + m_table->setTableData(td); + + m_parents.clear(); + m_allTransitions.clear(); + m_docStatesToQStates.clear(); + m_currentTransition = nullptr; + + return m_table; + } + +private: + using NodeVisitor::visit; + using ExecutableContent::Builder::createContext; + + bool visit(DocumentModel::Scxml *node) Q_DECL_OVERRIDE + { + m_table = new StateTable; + + switch (node->binding) { + case DocumentModel::Scxml::EarlyBinding: + m_table->setDataBinding(StateTable::EarlyBinding); + break; + case DocumentModel::Scxml::LateBinding: + m_table->setDataBinding(StateTable::LateBinding); + m_bindLate = true; + break; + default: + Q_UNREACHABLE(); + } + + m_table->setName(node->name); + + m_parents.append(m_table); + visit(node->children); + + m_dataElements.append(node->dataElements); + if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { + m_table->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(m_table, initialState)); + } + + switch (node->dataModel) { + case DocumentModel::Scxml::NullDataModel: + m_table->setDataModel(new NullDataModel(m_table)); + break; + case DocumentModel::Scxml::JSDataModel: + m_table->setDataModel(new EcmaScriptDataModel(m_table)); + break; + default: + Q_UNREACHABLE(); + } + + return false; + } + + bool visit(DocumentModel::State *node) Q_DECL_OVERRIDE + { + QAbstractState *newState = nullptr; + switch (node->type) { + case DocumentModel::State::Normal: { + auto s = new ScxmlState(currentParent()); + newState = s; + if (node->initialState) + m_initialStates.append(qMakePair(s, node->initialState)); + } break; + case DocumentModel::State::Parallel: { + auto s = new ScxmlState(currentParent()); + s->setChildMode(QState::ParallelStates); + newState = s; + } break; + case DocumentModel::State::Initial: { + auto s = new ScxmlState(currentParent()); + currentParent()->setInitialState(s); + newState = s; + } break; + case DocumentModel::State::Final: { + auto s = new ScxmlFinalState(currentParent()); + newState = s; + s->setDoneData(generate(node->doneData)); + } break; + default: + Q_UNREACHABLE(); + } + + newState->setObjectName(node->id); + + m_docStatesToQStates.insert(node, newState); + m_parents.append(newState); + + if (!node->dataElements.isEmpty()) { + if (m_bindLate) { + qobject_cast(newState)->setInitInstructions(startNewSequence()); + generate(node->dataElements); + endSequence(); + } else { + m_dataElements.append(node->dataElements); + } + } + + ExecutableContent::ContainerId onEntry = generate(node->onEntry); + ExecutableContent::ContainerId onExit = generate(node->onExit); + if (ScxmlState *s = qobject_cast(newState)) { + s->setOnEntryInstructions(onEntry); + s->setOnExitInstructions(onExit); + } else if (ScxmlFinalState *f = qobject_cast(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 + { + QState *parentState = 0; + if (QHistoryState *parent = qobject_cast(m_parents.last())) { + // QHistoryState cannot have an initial transition, only an initial state. + // So, work around that by creating an initial state, and add the transition to that. + parentState = new ScxmlState(parent->parentState()); + parent->setDefaultState(parentState); + } else { + parentState = currentParent(); + } + + auto newTransition = new ScxmlTransition(parentState, toUtf8(node->events)); + if (node->condition) { + auto cond = createEvaluatorBool(QStringLiteral("transition"), QStringLiteral("cond"), *node->condition.data()); + newTransition->setConditionalExpression(cond); + } + + parentState->addTransition(newTransition); + 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->table()); + return false; + } + + bool visit(DocumentModel::HistoryState *state) Q_DECL_OVERRIDE + { + QHistoryState *newState = new QHistoryState(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; + } + + void endVisit(DocumentModel::HistoryState *) Q_DECL_OVERRIDE + { + m_parents.removeLast(); + } + +private: // Utility methods + static QList toUtf8(const QStringList &l) + { + QList res; + foreach (const QString &s, l) + res.append(s.toUtf8()); + return res; + } + + QState *currentParent() const + { + if (m_parents.isEmpty()) + return nullptr; + + QState *parent = qobject_cast(m_parents.last()); + Q_ASSERT(parent); + return parent; + } + + void wireTransitions() + { + for (QHash::const_iterator i = m_allTransitions.begin(), ei = m_allTransitions.end(); i != ei; ++i) { + QList 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); + } + } + + 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); + } + } + + 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()); + } + } + + 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); + } + + DataModel *dataModel() const + { return m_table->dataModel(); } + +private: + QVector m_parents; + QHash m_allTransitions; + QHash m_docStatesToQStates; + QAbstractTransition *m_currentTransition = nullptr; + QVector> m_initialStates; + bool m_bindLate = false; + QVector m_dataElements; +}; + +ScxmlParser::ScxmlParser(QXmlStreamReader *reader, LoaderFunction loader) + : m_currentParent(0) + , m_currentState(0) + , m_loader(loader) + , m_reader(reader) + , m_state(StartingParsing) +{ } + +QString ScxmlParser::fileName() const +{ + return m_fileName; +} + +void ScxmlParser::setFileName(const QString &fileName) +{ + m_fileName = fileName; +} + +DocumentModel::AbstractState *ScxmlParser::currentParent() const +{ + DocumentModel::AbstractState *parent = m_currentParent->asAbstractState(); + Q_ASSERT(!m_currentParent || parent); + return parent; +} + +void ScxmlParser::parse() +{ + m_doc.reset(new DocumentModel::ScxmlDocument); + m_currentParent = m_doc->root; + m_currentState = m_doc->root; + while (!m_reader->atEnd()) { + QXmlStreamReader::TokenType tt = m_reader->readNext(); + switch (tt) { + case QXmlStreamReader::NoToken: + // The reader has not yet read anything. + continue; + case QXmlStreamReader::Invalid: + // An error has occurred, reported in error() and errorString(). + break; + case QXmlStreamReader::StartDocument: + // The reader reports the XML version number in documentVersion(), and the encoding + // as specified in the XML document in documentEncoding(). If the document is declared + // standalone, isStandaloneDocument() returns true; otherwise it returns false. + break; + case QXmlStreamReader::EndDocument: + // The reader reports the end of the document. + if (!m_stack.isEmpty() || m_state != FinishedParsing) { + addError(QStringLiteral("document finished without a proper scxml item")); + m_state = ParsingError; + } + break; + case QXmlStreamReader::StartElement: + // The reader reports the start of an element with namespaceUri() and name(). Empty + // elements are also reported as StartElement, followed directly by EndElement. + // The convenience function readElementText() can be called to concatenate all content + // until the corresponding EndElement. Attributes are reported in attributes(), + // namespace declarations in namespaceDeclarations(). + { + QStringRef elName = m_reader->name(); + QXmlStreamAttributes attributes = m_reader->attributes(); + if (!m_stack.isEmpty() && (m_stack.last().kind == ParserState::DataElement + || m_stack.last().kind == ParserState::Data)) { + /*switch (m_table->dataModel()) { + case StateTable::None: + break; // error? + case StateTable::Json: + case StateTable::Javascript: + { + ParserState pNew = ParserState(ParserState::DataElement); + QJsonObject obj; + foreach (const QXmlStreamAttribute &attribute, attributes) + obj.insert(QStringLiteral("@").append(attribute.name()), attribute.value().toString()); + pNew.jsonValue = obj; + m_stack.append(pNew); + break; + } + case StateTable::Xml: + { + ParserState pNew = ParserState(ParserState::DataElement); + Q_ASSERT(0); + } + }*/ + break; + } else if (elName == QLatin1String("scxml")) { + m_doc->root = new DocumentModel::Scxml(xmlLocation()); + m_doc->root->xmlLocation = xmlLocation(); + auto scxml = m_doc->root; + if (m_state != StartingParsing || !m_stack.isEmpty()) { + addError(xmlLocation(), QStringLiteral("found scxml tag mid stream")); + m_state = ParsingError; + return; + } else { + m_state = ParsingScxml; + } + if (!checkAttributes(attributes, "version|initial,datamodel,binding,name,classname")) return; + if (m_reader->namespaceUri() != QLatin1String("http://www.w3.org/2005/07/scxml")) { + addError(QStringLiteral("default namespace must be set with xmlns=\"http://www.w3.org/2005/07/scxml\" in the scxml tag")); + return; + } + if (attributes.value(QLatin1String("version")) != QLatin1String("1.0")) { + addError(QStringLiteral("unsupported scxml version, expected 1.0 in scxml tag")); + return; + } + ParserState pNew = ParserState(ParserState::Scxml); + pNew.initialId = attributes.value(QLatin1String("initial")).toUtf8(); + QStringRef datamodel = attributes.value(QLatin1String("datamodel")); + if (datamodel.isEmpty() || datamodel == QLatin1String("null")) { + scxml->dataModel = DocumentModel::Scxml::NullDataModel; + } else if (datamodel == QLatin1String("ecmascript")) { + scxml->dataModel = DocumentModel::Scxml::JSDataModel; + } else { + addError(QStringLiteral("Unsupported data model '%1' in scxml") + .arg(datamodel.toString())); + } + QStringRef binding = attributes.value(QLatin1String("binding")); + if (binding.isEmpty() || binding == QLatin1String("early")) { + scxml->binding = DocumentModel::Scxml::EarlyBinding; + } else if (binding == QLatin1String("late")) { + scxml->binding = DocumentModel::Scxml::LateBinding; + } else { + addError(QStringLiteral("Unsupperted binding type '%1'") + .arg(binding.toString())); + return; + } + QStringRef name = attributes.value(QLatin1String("name")); + if (!name.isEmpty()) { + scxml->name = name.toString(); + } + QStringRef qtClassname = attributes.value(qtScxmlNamespace, QStringLiteral("classname")); + if (!qtClassname.isEmpty()) { + scxml->qtClassname = qtClassname.toString(); + } + m_currentState = m_currentParent = m_doc->root; + pNew.instructionContainer = &m_doc->root->initialSetup; + m_stack.append(pNew); + } else if (elName == QLatin1String("state")) { + if (!checkAttributes(attributes, "|id,initial")) return; + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Normal, xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + ParserState pNew = ParserState(ParserState::State); + pNew.initialId = attributes.value(QLatin1String("initial")).toUtf8(); + m_currentState = m_currentParent = newState; + m_stack.append(pNew); + } else if (elName == QLatin1String("parallel")) { + if (!checkAttributes(attributes, "|id")) return; + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Parallel, xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + m_currentState = m_currentParent = newState; + m_stack.append(ParserState(ParserState::Parallel)); + } else if (elName == QLatin1String("initial")) { + if (!checkAttributes(attributes, "")) return; + if (currentParent()->asState()->type == DocumentModel::State::Parallel) { + addError(QStringLiteral("Explicit initial state for parallel states not supported (only implicitly through the initial states of its substates)")); + m_state = ParsingError; + return; + } + ParserState pNew(ParserState::Initial); + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Initial, xmlLocation()); + m_currentState = m_currentParent = newState; + m_stack.append(pNew); + } else if (elName == QLatin1String("transition")) { + if (!checkAttributes(attributes, "|event,cond,target,type")) return; + auto transition = m_doc->newTransition(m_currentParent, 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"))) + transition->condition.reset(new QString(attributes.value(QLatin1String("cond")).toString())); + QStringRef type = attributes.value(QLatin1String("type")); + if (type.isEmpty() || type == QLatin1String("external")) { + transition->type = DocumentModel::Transition::External; +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + } else if (type == QLatin1String("internal")) { + transition->type = DocumentModel::Transition::Internal; +#endif + } else { + addError(QStringLiteral("invalid transition type '%1', valid values are 'external' and 'internal'").arg(type.toString())); + m_state = ParsingError; + break; + } + ParserState pNew = ParserState(ParserState::Transition); + pNew.instructionContainer = &transition->instructionsOnTransition; + m_stack.append(pNew); + } else if (elName == QLatin1String("final")) { + if (!checkAttributes(attributes, "|id")) return; + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Final, xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + m_currentState = m_currentParent = newState; + m_stack.append(ParserState(ParserState::Final)); + } else if (elName == QLatin1String("history")) { + if (!checkAttributes(attributes, "|id,type")) return; + auto newState = m_doc->newHistoryState(currentParent(), xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + QStringRef type = attributes.value(QLatin1String("type")); + if (type.isEmpty() || type == QLatin1String("shallow")) { + newState->type = DocumentModel::HistoryState::Shallow; + } else if (type == QLatin1String("deep")) { + newState->type = DocumentModel::HistoryState::Deep; + } else { + addError(QStringLiteral("invalid history type %1, valid values are 'shallow' and 'deep'").arg(type.toString())); + m_state = ParsingError; + return; + } + ParserState pNew = ParserState(ParserState::History); + m_currentState = m_currentParent = newState; + m_stack.append(pNew); + } else if (elName == QLatin1String("onentry")) { + if (!checkAttributes(attributes, "")) return; + ParserState pNew(ParserState::OnEntry); + switch (m_stack.last().kind) { + case ParserState::Final: + case ParserState::State: + case ParserState::Parallel: + if (DocumentModel::State *s = m_currentState->asState()) { + pNew.instructionContainer = m_doc->newSequence(&s->onEntry); + break; + } + // intentional fall-through + default: + addError(QStringLiteral("unexpected container state for onentry")); + m_state = ParsingError; + break; + } + m_stack.append(pNew); + } else if (elName == QLatin1String("onexit")) { + if (!checkAttributes(attributes, "")) return; + ParserState pNew(ParserState::OnExit); + switch (m_stack.last().kind) { + case ParserState::Final: + case ParserState::State: + case ParserState::Parallel: + if (DocumentModel::State *s = m_currentState->asState()) { + pNew.instructionContainer = m_doc->newSequence(&s->onExit); + break; + } + // intentional fall-through + default: + addError(QStringLiteral("unexpected container state for onexit")); + m_state = ParsingError; + break; + } + m_stack.append(pNew); + } else if (elName == QLatin1String("raise")) { + if (!checkAttributes(attributes, "event")) return; + ParserState pNew = ParserState(ParserState::Raise); + auto raise = m_doc->newNode(xmlLocation()); + raise->event = attributes.value(QLatin1String("event")).toString(); + pNew.instruction = raise; + m_stack.append(pNew); + } else if (elName == QLatin1String("if")) { + if (!checkAttributes(attributes, "cond")) return; + ParserState pNew = ParserState(ParserState::If); + auto *ifI = m_doc->newNode(xmlLocation()); + pNew.instruction = ifI; + ifI->conditions.append(attributes.value(QLatin1String("cond")).toString()); + pNew.instructionContainer = m_doc->newSequence(&ifI->blocks); + m_stack.append(pNew); + } else if (elName == QLatin1String("elseif")) { + if (!checkAttributes(attributes, "cond")) return; + DocumentModel::If *ifI = m_stack.last().instruction->asIf(); + Q_ASSERT(ifI); + ifI->conditions.append(attributes.value(QLatin1String("cond")).toString()); + m_stack.last().instructionContainer = m_doc->newSequence(&ifI->blocks); + m_stack.append(ParserState(ParserState::ElseIf)); + } else if (elName == QLatin1String("else")) { + if (!checkAttributes(attributes, "")) return; + DocumentModel::If *ifI = m_stack.last().instruction->asIf(); + Q_ASSERT(ifI); + m_stack.last().instructionContainer = m_doc->newSequence(&ifI->blocks); + m_stack.append(ParserState(ParserState::Else)); + } else if (elName == QLatin1String("foreach")) { + if (!checkAttributes(attributes, "array,item|index")) return; + ParserState pNew = ParserState(ParserState::Foreach); + auto foreachI = m_doc->newNode(xmlLocation()); + foreachI->array = attributes.value(QLatin1String("array")).toString(); + foreachI->item = attributes.value(QLatin1String("item")).toString(); + foreachI->index = attributes.value(QLatin1String("index")).toString(); + pNew.instruction = foreachI; + pNew.instructionContainer = &foreachI->block; + m_stack.append(pNew); + } else if (elName == QLatin1String("log")) { + if (!checkAttributes(attributes, "|label,expr")) return; + ParserState pNew = ParserState(ParserState::Log); + auto logI = m_doc->newNode(xmlLocation()); + logI->label = attributes.value(QLatin1String("label")).toString(); + logI->expr = attributes.value(QLatin1String("expr")).toString(); + pNew.instruction = logI; + m_stack.append(pNew); + } else if (elName == QLatin1String("datamodel")) { + if (!checkAttributes(attributes, "")) return; + m_stack.append(ParserState(ParserState::DataModel)); + } else if (elName == QLatin1String("data")) { + if (!checkAttributes(attributes, "id|src,expr")) return; + auto data = m_doc->newNode(xmlLocation()); + data->id = attributes.value(QLatin1String("id")).toString(); + data->src = attributes.value(QLatin1String("src")).toString(); + data->expr = attributes.value(QLatin1String("expr")).toString(); + if (!data->src.isEmpty()) { + addError(QStringLiteral("the source attribute in a data tag is unsupported")); // FIXME: use a loader like in