From f9a620340ddce2a17fd775d1e210268cac13377b Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Thu, 15 May 2014 21:55:13 +0200 Subject: Introduced interpreter.step() --- README.md | 13 + contrib/java/.classpath | 5 +- contrib/java/.project | 5 + src/uscxml/Interpreter.cpp | 184 +++++--- src/uscxml/Interpreter.h | 19 +- src/uscxml/debug/Debugger.cpp | 4 +- src/uscxml/debug/SCXMLDotWriter.cpp | 12 +- src/uscxml/interpreter/InterpreterDraft6.cpp | 484 +++++++++++++-------- src/uscxml/interpreter/InterpreterDraft6.h | 5 +- .../ecmascript/JavaScriptCore/JSCDataModel.cpp | 4 +- .../datamodel/ecmascript/v8/V8DataModel.cpp | 4 +- .../plugins/datamodel/prolog/swi/SWIDataModel.cpp | 2 +- .../plugins/datamodel/xpath/XPathDataModel.cpp | 4 +- .../plugins/element/respond/RespondElement.cpp | 4 +- src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp | 5 +- src/uscxml/transform/ChartToFSM.cpp | 8 +- test/CMakeLists.txt | 5 + test/src/test-arabica-namespaces.cpp | 6 +- test/src/test-lifecycle.cpp | 129 ++++++ test/src/test-predicates.cpp | 38 +- 20 files changed, 643 insertions(+), 297 deletions(-) create mode 100644 test/src/test-lifecycle.cpp diff --git a/README.md b/README.md index 90d988b..7499b63 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,19 @@ or return immediately. You will have to call this method every now and then if y Interpreter scxml = Interpreter::fromXML(""); scxml.interpret(); // blocking +When using blocking interpretation, it is assumed that it is running on the main thread and +it will call runOnMainThread between stable configurations. + +### Interleaved Interpretation with inline SCXML + Interpreter scxml = Interpreter::fromXML(""); + InterpreterState state; + do { + state = interpreter.step(true); // boolean argument causes blocking or not + } while(state > 0) + +Using step, you can run a single macrostep of the interpreter and interleave +interpretation with the rest of your code. + ### Callbacks for an Interpreter You can register an InterpreterMonitor prior to start in order to receive diff --git a/contrib/java/.classpath b/contrib/java/.classpath index 7592c3b..0830f8a 100644 --- a/contrib/java/.classpath +++ b/contrib/java/.classpath @@ -1,11 +1,10 @@ + - - - + diff --git a/contrib/java/.project b/contrib/java/.project index 260e80f..bed7874 100644 --- a/contrib/java/.project +++ b/contrib/java/.project @@ -26,6 +26,11 @@ /Users/sradomski/Documents/TK/Code/uscxml/src/bindings/swig/java + java + 2 + /Users/sradomski/Documents/TK/Code/uscxml/src/bindings/swig/java + + uscxml 2 /Users/sradomski/Documents/TK/Code/uscxml/build/cli/src/bindings/swig/java diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 75d44c1..e2ee1da 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -292,9 +292,11 @@ InterpreterImpl::InterpreterImpl() { _running = false; _destroyed = false; _done = true; + _stable = false; _isInitialized = false; _httpServlet = NULL; _factory = NULL; + _sessionId = UUID::getUUID(); _capabilities = CAN_BASIC_HTTP | CAN_GENERIC_HTTP; _domEventListener._interpreter = this; @@ -389,7 +391,7 @@ Interpreter Interpreter::fromInputSource(Arabica::SAX::InputSource& if (parser.parse(source) && parser.getDocument() && parser.getDocument().hasChildNodes()) { interpreterImpl->setNameSpaceInfo(parser.nameSpace); interpreterImpl->_document = parser.getDocument(); - interpreterImpl->init(); +// interpreterImpl->init(); interpreter = Interpreter(interpreterImpl); _instances[interpreterImpl->getSessionId()] = interpreterImpl; } else { @@ -473,6 +475,7 @@ InterpreterImpl::~InterpreterImpl() { // tthread::lock_guard lock(_mutex); if (_thread) { if (_thread->get_id() != tthread::this_thread::get_id()) { + // unblock event queue Event event; event.name = "unblock.and.die"; @@ -488,8 +491,6 @@ InterpreterImpl::~InterpreterImpl() { if (_sendQueue) delete _sendQueue; -// if (_httpServlet) -// delete _httpServlet; } void InterpreterImpl::start() { @@ -550,52 +551,116 @@ bool InterpreterImpl::runOnMainThread(int fps, bool blocking) { } void InterpreterImpl::init() { - if (_document) { - NodeList scxmls; - if (_nsInfo.nsURL.size() == 0) { - scxmls = _document.getElementsByTagName("scxml"); - } else { - scxmls = _document.getElementsByTagNameNS(_nsInfo.nsURL, "scxml"); - } - if (scxmls.getLength() > 0) { - _scxml = (Arabica::DOM::Element)scxmls.item(0); + if (!_document) { + LOG(ERROR) << "Interpreter has no DOM at all!" << std::endl; + _done = true; + return; + } + + // find scxml element + NodeList scxmls; + if (_nsInfo.nsURL.size() == 0) { + scxmls = _document.getElementsByTagName("scxml"); + } else { + scxmls = _document.getElementsByTagNameNS(_nsInfo.nsURL, "scxml"); + } + + if (scxmls.getLength() > 0) { + _scxml = (Arabica::DOM::Element)scxmls.item(0); - // setup xpath and check that it works - if (_nsInfo.getNSContext() != NULL) - _xpath.setNamespaceContext(*_nsInfo.getNSContext()); + // setup xpath and check that it works + if (_nsInfo.getNSContext() != NULL) + _xpath.setNamespaceContext(*_nsInfo.getNSContext()); - if (_name.length() == 0) - _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : UUID::getUUID()); + if (_name.length() == 0) + _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : UUID::getUUID()); - normalize(_scxml); + // normalize document + normalize(_scxml); - // setup event queue for delayed send - _sendQueue = new DelayedEventQueue(); - _sendQueue->start(); + // setup event queue for delayed send + _sendQueue = new DelayedEventQueue(); + _sendQueue->start(); - // register for dom events to manage cached states - Arabica::DOM::Events::EventTarget eventTarget(_scxml); - eventTarget.addEventListener("DOMNodeInserted", _domEventListener, true); - eventTarget.addEventListener("DOMNodeRemoved", _domEventListener, true); - eventTarget.addEventListener("DOMSubtreeModified", _domEventListener, true); + // register for dom events to manage cached states + Arabica::DOM::Events::EventTarget eventTarget(_scxml); + eventTarget.addEventListener("DOMNodeInserted", _domEventListener, true); + eventTarget.addEventListener("DOMNodeRemoved", _domEventListener, true); + eventTarget.addEventListener("DOMSubtreeModified", _domEventListener, true); - if (_factory == NULL) - _factory = Factory::getInstance(); + if (_factory == NULL) + _factory = Factory::getInstance(); - } else { - LOG(ERROR) << "Cannot find SCXML element" << std::endl; - _done = true; - return; - } } else { - LOG(ERROR) << "Interpreter has no DOM at all!" << std::endl; + LOG(ERROR) << "Cannot find SCXML element" << std::endl; _done = true; + return; } - + if (_sessionId.length() == 0) _sessionId = UUID::getUUID(); + + setupIOProcessors(); + + std::string datamodelName; + if (HAS_ATTR(_scxml, "datamodel")) { + datamodelName = ATTR(_scxml, "datamodel"); + } else if (HAS_ATTR(_scxml, "profile")) {// SCION SCXML uses profile to specify datamodel + datamodelName = ATTR(_scxml, "profile"); + } + + if(datamodelName.length() > 0) { + _dataModel = _factory->createDataModel(datamodelName, this); + if (!_dataModel) { + Event e; + e.data.compound["cause"] = Data("Cannot instantiate datamodel", Data::VERBATIM); + throw e; + } + } else { + _dataModel = _factory->createDataModel("null", this); + } + + _dataModel.assign("_x.args", _cmdLineOptions); + + _running = true; +#if VERBOSE + std::cout << "running " << this << std::endl; +#endif + + _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); + + // @TODO: Reread http://www.w3.org/TR/scxml/#DataBinding + + if (_binding == EARLY) { + // initialize all data elements + NodeSet dataElems = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); + for (unsigned int i = 0; i < dataElems.size(); i++) { + // do not process data elements of nested documents from invokers + if (!getAncestorElement(dataElems[i], _nsInfo.xmlNSPrefix + "invoke")) + if (dataElems[i].getNodeType() == Node_base::ELEMENT_NODE) { + initializeData(Element(dataElems[i])); + } + } + } else { + // initialize current data elements + NodeSet topDataElems = filterChildElements(_nsInfo.xmlNSPrefix + "data", filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", _scxml)); + for (unsigned int i = 0; i < topDataElems.size(); i++) { + if (topDataElems[i].getNodeType() == Node_base::ELEMENT_NODE) + initializeData(Element(topDataElems[i])); + } + } + + // executeGlobalScriptElements + NodeSet globalScriptElems = filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml); + for (unsigned int i = 0; i < globalScriptElems.size(); i++) { + if (_dataModel) { + executeContent(globalScriptElems[i]); + } + } + _isInitialized = true; + _stable = false; } /** @@ -641,11 +706,13 @@ void InterpreterImpl::initializeData(const Element& data) { void InterpreterImpl::normalize(Arabica::DOM::Element& scxml) { // TODO: Resolve XML includes - // make sure every state has an id and set isFirstEntry to true (replaced by _alreadyEntered NodeSet) - Arabica::XPath::NodeSet states = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "state", _scxml).asNodeSet(); + // make sure every state has an id + Arabica::XPath::NodeSet states; + states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "state", _scxml).asNodeSet()); + states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "final", _scxml).asNodeSet()); + states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "history", _scxml).asNodeSet()); for (int i = 0; i < states.size(); i++) { Arabica::DOM::Element stateElem = Arabica::DOM::Element(states[i]); -// stateElem.setAttribute("isFirstEntry", "true"); if (!stateElem.hasAttribute("id")) { stateElem.setAttribute("id", UUID::getUUID()); } @@ -658,45 +725,25 @@ void InterpreterImpl::normalize(Arabica::DOM::Element& scxml) { if (!invokeElem.hasAttribute("id") && !invokeElem.hasAttribute("idlocation")) { invokeElem.setAttribute("id", UUID::getUUID()); } -// // make sure every finalize element contained has the invoke id as an attribute -// Arabica::XPath::NodeSet finalizes = _xpath.evaluate("" + _xpathPrefix + "finalize", invokeElem).asNodeSet(); -// for (int j = 0; j < finalizes.size(); j++) { -// Arabica::DOM::Element finalizeElem = Arabica::DOM::Element(finalizes[j]); -// finalizeElem.setAttribute("invokeid", invokeElem.getAttribute("id")); -// } - } - - Arabica::XPath::NodeSet finals = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "final", _scxml).asNodeSet(); - for (int i = 0; i < finals.size(); i++) { - Arabica::DOM::Element finalElem = Arabica::DOM::Element(finals[i]); -// finalElem.setAttribute("isFirstEntry", "true"); - if (!finalElem.hasAttribute("id")) { - finalElem.setAttribute("id", UUID::getUUID()); - } - } - - Arabica::XPath::NodeSet histories = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "history", _scxml).asNodeSet(); - for (int i = 0; i < histories.size(); i++) { - Arabica::DOM::Element historyElem = Arabica::DOM::Element(histories[i]); - if (!historyElem.hasAttribute("id")) { - historyElem.setAttribute("id", UUID::getUUID()); - } } if (!scxml.hasAttribute("id")) { scxml.setAttribute("id", UUID::getUUID()); } - } void InterpreterImpl::receiveInternal(const Event& event) { -// std::cout << _name << " receiveInternal: " << event.name << std::endl; +#if VERBOSE + std::cout << _name << " receiveInternal: " << event.name << std::endl; +#endif _internalQueue.push_back(event); // _condVar.notify_all(); } void InterpreterImpl::receive(const Event& event, bool toFront) { -// std::cout << _name << " receive: " << event.name << std::endl; +#if VERBOSE + std::cout << _name << " receive: " << event.name << std::endl; +#endif if (toFront) { _externalQueue.push_front(event); } else { @@ -1562,11 +1609,16 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node& cont if (_dataModel) { if (HAS_ATTR(content, "src")) { URL scriptUrl(ATTR(content, "src")); - if (!scriptUrl.toAbsolute(_baseURI)) { + if (!scriptUrl.isAbsolute() && !_baseURI) { LOG(ERROR) << "script element has relative URI " << ATTR(content, "src") << " with no base URI set for interpreter"; return; } + if (!scriptUrl.toAbsolute(_baseURI)) { + LOG(ERROR) << "Failed to convert relative script URI " << ATTR(content, "src") << " to absolute with base URI " << _baseURI.asString(); + return; + } + std::stringstream srcContent; try { if (_cachedURLs.find(scriptUrl.asString()) != _cachedURLs.end() && false) { @@ -2434,7 +2486,7 @@ void InterpreterImpl::DOMEventListener::handleEvent(Arabica::DOM::Events::Event< if (event.getType().compare("DOMAttrModified") == 0) // we do not care about attributes return; Node target = Arabica::DOM::Node(event.getTarget()); - NodeSet childs = Interpreter::filterChildElements(_interpreter->_nsInfo.xmlNSPrefix + "state", target); + NodeSet childs = InterpreterImpl::filterChildElements(_interpreter->_nsInfo.xmlNSPrefix + "state", target); for (int i = 0; i < childs.size(); i++) { if (HAS_ATTR(childs[i], "id")) { _interpreter->_cachedStates.erase(ATTR(childs[i], "id")); diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 6a11c46..93062e6 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -188,6 +188,16 @@ private: void init(const std::map& nsInfo); }; +// values larger than 0 indicate that you ought to step again +enum InterpreterState { + FINISHED = 0, // machine reached a final configuration + INIT_FAILED = -1, // could not initialize interpreter + INTERRUPTED = -2, // machine was interrupted + NOTHING_TODO = 4, // when non-blocking returns nothing to do + PROCESSED = 8, // an event was processed + INITIALIZED = 16 // initial stable configuration was assumed +}; + class USCXML_API InterpreterImpl : public boost::enable_shared_from_this { public: @@ -214,7 +224,8 @@ public: } /// This one ought to be pure, but SWIG will generate gibberish if it is - virtual void interpret() {}; + virtual void interpret() = 0; + virtual InterpreterState step(bool blocking = false) = 0; void addMonitor(InterpreterMonitor* monitor) { _monitors.insert(monitor); @@ -535,6 +546,10 @@ public: _impl->interpret(); }; + InterpreterState step(bool blocking = false) { + return _impl->step(blocking); + }; + void addMonitor(InterpreterMonitor* monitor) { return _impl->addMonitor(monitor); } @@ -684,6 +699,7 @@ public: return _impl->isLegalConfiguration(config); } +#if 0 static bool isState(const Arabica::DOM::Node& state) { return InterpreterImpl::isState(state); } @@ -773,6 +789,7 @@ public: Arabica::XPath::NodeSet getProperAncestors(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2) { return _impl->getProperAncestors(s1, s2); } +#endif boost::shared_ptr getImpl() const { return _impl; diff --git a/src/uscxml/debug/Debugger.cpp b/src/uscxml/debug/Debugger.cpp index d1a8068..b74b2a9 100644 --- a/src/uscxml/debug/Debugger.cpp +++ b/src/uscxml/debug/Debugger.cpp @@ -66,8 +66,8 @@ std::list getQualifiedInvokeBreakpoints(Interpreter interpreter, con std::list getQualifiedTransBreakpoints(Interpreter interpreter, const Arabica::DOM::Element& transition, Breakpoint breakpointTemplate) { std::list breakpoints; - Arabica::DOM::Element source(interpreter.getSourceState(transition)); - Arabica::XPath::NodeSet targets = interpreter.getTargetStates(transition); + Arabica::DOM::Element source(interpreter.getImpl()->getSourceState(transition)); + Arabica::XPath::NodeSet targets = interpreter.getImpl()->getTargetStates(transition); for (int j = 0; j < targets.size(); j++) { Arabica::DOM::Element target(targets[j]); diff --git a/src/uscxml/debug/SCXMLDotWriter.cpp b/src/uscxml/debug/SCXMLDotWriter.cpp index 2058d72..93f09ab 100644 --- a/src/uscxml/debug/SCXMLDotWriter.cpp +++ b/src/uscxml/debug/SCXMLDotWriter.cpp @@ -110,7 +110,7 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Arabica::DOM::Ele return; _knownIds.insert(elemId); - bool subgraph = Interpreter::isCompound(elem) || Interpreter::isParallel(elem); + bool subgraph = InterpreterImpl::isCompound(elem) || InterpreterImpl::isParallel(elem); if (subgraph) { _indentation++; os << getPrefix() << "subgraph \"cluster_" << elemId << "\" {" << std::endl; @@ -122,11 +122,11 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Arabica::DOM::Ele os << "label=<State
" << nameForNode(elem) << ">,"; // is the state initial? - if (_interpreter.isInitial(elem)) + if (_interpreter.getImpl()->isInitial(elem)) os << "style=filled, fillcolor=lightgrey, "; // is this state final? - if (_interpreter.isFinal(elem)) + if (_interpreter.getImpl()->isFinal(elem)) os << "shape=doublecircle,"; // is the current state in the basic configuration? @@ -176,7 +176,7 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Arabica::DOM::Ele os << "]" << std::endl; } } - if (Interpreter::isState(childElems.item(i))) { + if (InterpreterImpl::isState(childElems.item(i))) { writeStateElement(os, (Arabica::DOM::Element)childElems.item(i)); } if (childElems.item(i).getNodeType() == Node_base::ELEMENT_NODE && iequals(TAGNAME(childElems.item(i)), "initial")) { @@ -200,7 +200,7 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Arabica::DOM::Ele void SCXMLDotWriter::writeTransitionElement(std::ostream& os, const Arabica::DOM::Element& elem) { std::string elemId = idForNode(elem); - Arabica::XPath::NodeSet targetStates = _interpreter.getTargetStates(elem); + Arabica::XPath::NodeSet targetStates = _interpreter.getImpl()->getTargetStates(elem); bool active = (elem == _transition); @@ -256,7 +256,7 @@ std::string SCXMLDotWriter::getDetailedLabel(const Arabica::DOM::Element lock(_mutex); - if (!_isInitialized) - init(); + InterpreterState state; + while(true) { + state = step(true); + + switch (state) { + case uscxml::INIT_FAILED: + case uscxml::FINISHED: + case uscxml::INTERRUPTED: + // return as we finished + return; + case uscxml::NOTHING_TODO: + // die as this can never happen with a blocking call + assert(false); + case uscxml::INITIALIZED: + case uscxml::PROCESSED: + + // process invokers on main thread + if(_thread == NULL) { + runOnMainThread(200); + } - if (!_scxml) { - return; + // process next step + break; } - // dump(); - - // just make sure we have a session id - assert(_sessionId.length() > 0); + } +} - setupIOProcessors(); +// setup / fetch the documents initial transitions +NodeSet InterpreterDraft6::getDocumentInitialTransitions() { + NodeSet initialTransitions; + + if (_userDefinedStartConfiguration.size() > 0) { + // we emulate entering a given configuration by creating a pseudo deep history + Element initHistory = _document.createElementNS(_nsInfo.nsURL, "history"); + _nsInfo.setPrefix(initHistory); + + initHistory.setAttribute("id", UUID::getUUID()); + initHistory.setAttribute("type", "deep"); + _scxml.insertBefore(initHistory, _scxml.getFirstChild()); + + std::string histId = ATTR(initHistory, "id"); + NodeSet histStates; + for (int i = 0; i < _userDefinedStartConfiguration.size(); i++) { + histStates.push_back(getState(_userDefinedStartConfiguration[i])); + } + _historyValue[histId] = histStates; + + Element initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); + _nsInfo.setPrefix(initialElem); + + initialElem.setAttribute("generated", "true"); + Element transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); + _nsInfo.setPrefix(transitionElem); + + transitionElem.setAttribute("target", histId); + initialElem.appendChild(transitionElem); + _scxml.appendChild(initialElem); + initialTransitions.push_back(transitionElem); + + } else { + // try to get initial transition from initial element + initialTransitions = _xpath.evaluate("/" + _nsInfo.xpathPrefix + "initial/" + _nsInfo.xpathPrefix + "transition", _scxml).asNodeSet(); + if (initialTransitions.size() == 0) { + Arabica::XPath::NodeSet initialStates; + // fetch per draft + initialStates = getInitialStates(); + assert(initialStates.size() > 0); + for (int i = 0; i < initialStates.size(); i++) { + Element initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); + _nsInfo.setPrefix(initialElem); + + initialElem.setAttribute("generated", "true"); + Element transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); + _nsInfo.setPrefix(transitionElem); + + transitionElem.setAttribute("target", ATTR(initialStates[i], "id")); + initialElem.appendChild(transitionElem); + _scxml.appendChild(initialElem); + initialTransitions.push_back(transitionElem); + } + } + } + return initialTransitions; +} + +// a macrostep +InterpreterState InterpreterDraft6::step(bool blocking) { + try { - std::string datamodelName; - if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "datamodel")) - datamodelName = ATTR(_scxml, "datamodel"); - if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "profile")) // SCION SCXML uses profile to specify datamodel - datamodelName = ATTR(_scxml, "profile"); - if(datamodelName.length() > 0) { - _dataModel = _factory->createDataModel(datamodelName, this); - if (!_dataModel) { - Event e; - e.data.compound["cause"] = Data("Cannot instantiate datamodel", Data::VERBATIM); - throw e; + monIter_t monIter; + NodeSet enabledTransitions; + + // setup document and interpreter + if (!_isInitialized) + init(); + + // if we failed return false + if (!_isInitialized) + return INIT_FAILED; + + // run initial transitions + if (!_stable) { + stabilize(); + // we might only need a single step + if (!_running) + goto EXIT_INTERPRETER; + return INITIALIZED; + } + + if (!_running) + return FINISHED; + + // read an external event and react + if (blocking) { + // wait until an event becomes available + while(_externalQueue.isEmpty()) { + _condVar.wait(_mutex); } } else { - _dataModel = _factory->createDataModel("null", this); - } - if(datamodelName.length() > 0 && !_dataModel) { - LOG(ERROR) << "No datamodel for " << datamodelName << " registered"; + // return immediately if external queue is empty + if (_externalQueue.isEmpty()) + return NOTHING_TODO; } + _currEvent = _externalQueue.pop(); - if (_dataModel) { - _dataModel.assign("_x.args", _cmdLineOptions); + #if VERBOSE + std::cout << "Received externalEvent event " << _currEvent.name << std::endl; + if (_running && _currEvent.name == "unblock.and.die") { + std::cout << "Still running " << this << std::endl; + } else { + std::cout << "Aborting " << this << std::endl; } + #endif + _currEvent.eventType = Event::EXTERNAL; // make sure it is set to external - _running = true; -#if VERBOSE - std::cout << "running " << this << std::endl; -#endif - - _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); - - // @TODO: Reread http://www.w3.org/TR/scxml/#DataBinding - - if (_dataModel && _binding == EARLY) { - // initialize all data elements - NodeSet dataElems = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); - for (unsigned int i = 0; i < dataElems.size(); i++) { - // do not process data elements of nested documents from invokers - if (!getAncestorElement(dataElems[i], _nsInfo.xmlNSPrefix + "invoke")) - if (dataElems[i].getNodeType() == Node_base::ELEMENT_NODE) { - initializeData(Element(dataElems[i])); - } - } - } else if(_dataModel) { - // initialize current data elements - NodeSet topDataElems = filterChildElements(_nsInfo.xmlNSPrefix + "data", filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", _scxml)); - for (unsigned int i = 0; i < topDataElems.size(); i++) { - if (topDataElems[i].getNodeType() == Node_base::ELEMENT_NODE) - initializeData(Element(topDataElems[i])); + // when we were blocking on destructor invocation + if (!_running) { + goto EXIT_INTERPRETER; + return INTERRUPTED; + } + // --- MONITOR: beforeProcessingEvent ------------------------------ + for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { + try { + (*monIter)->beforeProcessingEvent(shared_from_this(), _currEvent); } + USCXML_MONITOR_CATCH_BLOCK(beforeProcessingEvent) + } + + if (iequals(_currEvent.name, "cancel.invoke." + _sessionId)) + return INTERRUPTED; + + try { + _dataModel.setEvent(_currEvent); + } catch (Event e) { + LOG(ERROR) << "Syntax error while setting external event:" << std::endl << e << std::endl << _currEvent; } - // executeGlobalScriptElements - NodeSet globalScriptElems = filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml); - for (unsigned int i = 0; i < globalScriptElems.size(); i++) { - if (_dataModel) { - executeContent(globalScriptElems[i]); + for (std::map::iterator invokeIter = _invokers.begin(); + invokeIter != _invokers.end(); + invokeIter++) { + if (iequals(invokeIter->first, _currEvent.invokeid)) { + Arabica::XPath::NodeSet finalizes = filterChildElements(_nsInfo.xmlNSPrefix + "finalize", invokeIter->second.getElement()); + for (int k = 0; k < finalizes.size(); k++) { + Element finalizeElem = Element(finalizes[k]); + executeContent(finalizeElem); + } + } + if (HAS_ATTR(invokeIter->second.getElement(), "autoforward") && DOMUtils::attributeIsTrue(ATTR(invokeIter->second.getElement(), "autoforward"))) { + try { + // do not autoforward to invokers that send to #_parent from the SCXML IO Processor! + // Yes do so, see test229! + // if (!boost::equals(_currEvent.getOriginType(), "http://www.w3.org/TR/scxml/#SCXMLEventProcessor")) + invokeIter->second.send(_currEvent); + } catch(...) { + LOG(ERROR) << "Exception caught while sending event to invoker " << invokeIter->first; + } } } - NodeSet initialTransitions; - - if (_userDefinedStartConfiguration.size() > 0) { - // we emulate entering a given configuration by creating a pseudo deep history - Element initHistory = _document.createElementNS(_nsInfo.nsURL, "history"); - _nsInfo.setPrefix(initHistory); - - initHistory.setAttribute("id", UUID::getUUID()); - initHistory.setAttribute("type", "deep"); - _scxml.insertBefore(initHistory, _scxml.getFirstChild()); + + // run internal processing until we reach a stable configuration again + enabledTransitions = selectTransitions(_currEvent.name); + if (!enabledTransitions.empty()) { + // test 403b + enabledTransitions.to_document_order(); + microstep(enabledTransitions); + } - std::string histId = ATTR(initHistory, "id"); - NodeSet histStates; - for (int i = 0; i < _userDefinedStartConfiguration.size(); i++) { - histStates.push_back(getState(_userDefinedStartConfiguration[i])); + stabilize(); + return PROCESSED; + + EXIT_INTERPRETER: + if (!_running) { + // --- MONITOR: beforeCompletion ------------------------------ + for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { + try { + (*monIter)->beforeCompletion(shared_from_this()); + } + USCXML_MONITOR_CATCH_BLOCK(beforeCompletion) } - _historyValue[histId] = histStates; - - Element initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); - _nsInfo.setPrefix(initialElem); - - initialElem.setAttribute("generated", "true"); - Element transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); - _nsInfo.setPrefix(transitionElem); - - transitionElem.setAttribute("target", histId); - initialElem.appendChild(transitionElem); - _scxml.appendChild(initialElem); - initialTransitions.push_back(transitionElem); - - } else { - // try to get initial transition from initial element - initialTransitions = _xpath.evaluate("/" + _nsInfo.xpathPrefix + "initial/" + _nsInfo.xpathPrefix + "transition", _scxml).asNodeSet(); - if (initialTransitions.size() == 0) { - Arabica::XPath::NodeSet initialStates; - // fetch per draft - initialStates = getInitialStates(); - assert(initialStates.size() > 0); - for (int i = 0; i < initialStates.size(); i++) { - Element initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); - _nsInfo.setPrefix(initialElem); - - initialElem.setAttribute("generated", "true"); - Element transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); - _nsInfo.setPrefix(transitionElem); - - transitionElem.setAttribute("target", ATTR(initialStates[i], "id")); - initialElem.appendChild(transitionElem); - _scxml.appendChild(initialElem); - initialTransitions.push_back(transitionElem); + + exitInterpreter(); + if (_sendQueue) { + std::map >::iterator sendIter = _sendIds.begin(); + while(sendIter != _sendIds.end()) { + _sendQueue->cancelEvent(sendIter->first); + sendIter++; + } + } + + // --- MONITOR: afterCompletion ------------------------------ + for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { + try { + (*monIter)->afterCompletion(shared_from_this()); } + USCXML_MONITOR_CATCH_BLOCK(afterCompletion) } + return FINISHED; } + + assert(hasLegalConfiguration()); + _mutex.unlock(); - assert(initialTransitions.size() > 0); + // remove datamodel + if(_dataModel) + _dataModel = DataModel(); - enterStates(initialTransitions); - // _mutex.unlock(); - - // assert(hasLegalConfiguration()); - mainEventLoop(); + return PROCESSED; + } catch (boost::bad_weak_ptr e) { LOG(ERROR) << "Unclean shutdown " << std::endl << std::endl; + return INTERRUPTED; } + // set datamodel to null from this thread if(_dataModel) _dataModel = DataModel(); } +// process transitions until we are in a stable configuration again +void InterpreterDraft6::stabilize() { + + monIter_t monIter; + NodeSet enabledTransitions; + _stable = false; + + if (_configuration.size() == 0) { + // goto initial configuration + NodeSet initialTransitions = getDocumentInitialTransitions(); + assert(initialTransitions.size() > 0); + enterStates(initialTransitions); + } + + do { // process microsteps for enabled transitions until there are no more left + + enabledTransitions = selectEventlessTransitions(); + + if (enabledTransitions.size() == 0) { + if (_internalQueue.size() == 0) { + _stable = true; + } else { + _currEvent = _internalQueue.front(); + _internalQueue.pop_front(); +#if VERBOSE + std::cout << "Received internal event " << _currEvent.name << std::endl; +#endif + + // --- MONITOR: beforeProcessingEvent ------------------------------ + for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { + try { + (*monIter)->beforeProcessingEvent(shared_from_this(), _currEvent); + } + USCXML_MONITOR_CATCH_BLOCK(beforeProcessingEvent) + } + + if (_dataModel) + _dataModel.setEvent(_currEvent); + enabledTransitions = selectTransitions(_currEvent.name); + } + } + + if (!enabledTransitions.empty()) { + // test 403b + enabledTransitions.to_document_order(); + microstep(enabledTransitions); + } + } while(!_internalQueue.empty() || !_stable); + + monIter = _monitors.begin(); + + // --- MONITOR: onStableConfiguration ------------------------------ + for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { + try { + (*monIter)->onStableConfiguration(shared_from_this()); + } + USCXML_MONITOR_CATCH_BLOCK(onStableConfiguration) + } + + // when we reach a stable configuration, invoke + for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { + NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); + for (unsigned int j = 0; j < invokes.size(); j++) { + if (!HAS_ATTR(invokes[j], "persist") || !DOMUtils::attributeIsTrue(ATTR(invokes[j], "persist"))) { + invoke(invokes[j]); + } + } + } + _statesToInvoke = NodeSet(); + +} +#if 0 void InterpreterDraft6::mainEventLoop() { monIter_t monIter; @@ -234,24 +403,15 @@ void InterpreterDraft6::mainEventLoop() { } } } - _statesToInvoke = NodeSet(); + if (!_internalQueue.empty()) continue; // assume that we have a legal configuration as soon as the internal queue is empty assert(hasLegalConfiguration()); -#if 0 - std::cout << "Configuration: "; - for (int i = 0; i < _configuration.size(); i++) { - std::cout << ATTR(_configuration[i], "id") << ", "; - } - std::cout << std::endl; -#endif - monIter = _monitors.begin(); -// if (!_sendQueue || _sendQueue->isEmpty()) { // --- MONITOR: onStableConfiguration ------------------------------ for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { @@ -261,9 +421,8 @@ void InterpreterDraft6::mainEventLoop() { USCXML_MONITOR_CATCH_BLOCK(onStableConfiguration) } -// } - _mutex.unlock(); + // whenever we have a stable configuration, run the mainThread hooks with 200fps while(_externalQueue.isEmpty() && _thread == NULL) { runOnMainThread(200); @@ -304,46 +463,6 @@ void InterpreterDraft6::mainEventLoop() { LOG(ERROR) << "Syntax error while setting external event:" << std::endl << e << std::endl << _currEvent; } } -#if 0 - for (unsigned int i = 0; i < _configuration.size(); i++) { - NodeSet invokes = filterChildElements(_xmlNSPrefix + "invoke", _configuration[i]); - for (unsigned int j = 0; j < invokes.size(); j++) { - Element invokeElem = (Element)invokes[j]; - std::string invokeId; - if (HAS_ATTR(invokeElem, "id")) { - invokeId = ATTR(invokeElem, "id"); - } else { - if (HAS_ATTR(invokeElem, "idlocation") && _dataModel) { - try { - invokeId = _dataModel.evalAsString(ATTR(invokeElem, "idlocation")); - } catch(Event e) { - LOG(ERROR) << "Syntax error while assigning idlocation from invoke:" << std::endl << e << std::endl; - } - } - } - std::string autoForward = invokeElem.getAttribute("autoforward"); - if (iequals(invokeId, _currEvent.invokeid)) { - - Arabica::XPath::NodeSet finalizes = filterChildElements(_xmlNSPrefix + "finalize", invokeElem); - for (int k = 0; k < finalizes.size(); k++) { - Element finalizeElem = Element(finalizes[k]); - executeContent(finalizeElem); - } - - } - if (iequals(autoForward, "true")) { - try { - // do not autoforward to invokers that send to #_parent from the SCXML IO Processor! - // Yes do so, see test229! - // if (!boost::equals(_currEvent.getOriginType(), "http://www.w3.org/TR/scxml/#SCXMLEventProcessor")) - _invokers[invokeId].send(_currEvent); - } catch(...) { - LOG(ERROR) << "Exception caught while sending event to invoker " << invokeId; - } - } - } - } -#else for (std::map::iterator invokeIter = _invokers.begin(); invokeIter != _invokers.end(); invokeIter++) { @@ -365,7 +484,6 @@ void InterpreterDraft6::mainEventLoop() { } } } -#endif enabledTransitions = selectTransitions(_currEvent.name); if (!enabledTransitions.empty()) { // test 403b @@ -401,7 +519,8 @@ EXIT_INTERPRETER: } } - +#endif + Arabica::XPath::NodeSet InterpreterDraft6::selectTransitions(const std::string& event) { Arabica::XPath::NodeSet enabledTransitions; @@ -627,7 +746,7 @@ NEXT_ANCESTOR: } void InterpreterDraft6::microstep(const Arabica::XPath::NodeSet& enabledTransitions) { -#if 0 +#if VERBOSE std::cout << "Transitions: "; for (int i = 0; i < enabledTransitions.size(); i++) { std::cout << ((Element)getSourceState(enabledTransitions[i])).getAttribute("id") << " -> " << std::endl; @@ -685,6 +804,9 @@ void InterpreterDraft6::microstep(const Arabica::XPath::NodeSet& en } void InterpreterDraft6::exitInterpreter() { +#if VERBOSE + std::cout << "Exiting interpreter " << _name << std::endl; +#endif NodeSet statesToExit = _configuration; statesToExit.forward(false); statesToExit.sort(); @@ -712,7 +834,7 @@ void InterpreterDraft6::exitStates(const Arabica::XPath::NodeSet& e monIter_t monIter; #if VERBOSE - std::cout << "Enabled exit transitions: " << std::endl; + std::cout << _name << ": Enabled exit transitions: " << std::endl; for (int i = 0; i < enabledTransitions.size(); i++) { std::cout << enabledTransitions[i] << std::endl; } @@ -741,7 +863,7 @@ void InterpreterDraft6::exitStates(const Arabica::XPath::NodeSet& e tmpStates.push_back(source); tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end()); #if VERBOSE - std::cout << "tmpStates: "; + std::cout << _name << ": tmpStates: "; for (int i = 0; i < tmpStates.size(); i++) { std::cout << ATTR(tmpStates[i], "id") << ", "; } @@ -750,7 +872,7 @@ void InterpreterDraft6::exitStates(const Arabica::XPath::NodeSet& e ancestor = findLCCA(tmpStates); } #if VERBOSE - std::cout << "Ancestor: " << ATTR(ancestor, "id") << std::endl;; + std::cout << _name << ": Ancestor: " << ATTR(ancestor, "id") << std::endl;; #endif for (int j = 0; j < _configuration.size(); j++) { if (isDescendant(_configuration[j], ancestor)) @@ -772,7 +894,7 @@ void InterpreterDraft6::exitStates(const Arabica::XPath::NodeSet& e statesToExit.sort(); #if VERBOSE - std::cout << "States to exit: "; + std::cout << _name << ": States to exit: "; for (int i = 0; i < statesToExit.size(); i++) { std::cout << LOCALNAME(statesToExit[i]) << ":" << ATTR(statesToExit[i], "id") << ", "; } @@ -795,8 +917,8 @@ void InterpreterDraft6::exitStates(const Arabica::XPath::NodeSet& e } } _historyValue[historyElem.getAttribute("id")] = historyNodes; -#if 0 - std::cout << "History node " << ATTR(historyElem, "id") << " contains: "; +#if VERBOSE + std::cout << _name << ": History node " << ATTR(historyElem, "id") << " contains: "; for (int i = 0; i < historyNodes.size(); i++) { std::cout << ATTR(historyNodes[i], "id") << ", "; } @@ -857,9 +979,9 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& monIter_t monIter; #if VERBOSE - std::cout << "Enabled enter transitions: " << std::endl; + std::cout << _name << ": Enabled enter transitions: " << std::endl; for (int i = 0; i < enabledTransitions.size(); i++) { - std::cout << enabledTransitions[i] << std::endl; + std::cout << "\t" << enabledTransitions[i] << std::endl; } std::cout << std::endl; #endif @@ -871,7 +993,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& NodeSet tStates = getTargetStates(transition); #if VERBOSE - std::cout << "Target States: "; + std::cout << _name << ": Target States: "; for (int i = 0; i < tStates.size(); i++) { std::cout << ATTR(tStates[i], "id") << ", "; } @@ -881,7 +1003,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& Node ancestor; Node source = getSourceState(transition); #if VERBOSE - std::cout << "Source States: " << ATTR(source, "id") << std::endl; + std::cout << _name << ": Source States: " << ATTR(source, "id") << std::endl; #endif assert(source); @@ -905,7 +1027,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& } #if VERBOSE - std::cout << "Ancestor: " << ATTR(ancestor, "id") << std::endl; + std::cout << _name << ": Ancestor: " << ATTR(ancestor, "id") << std::endl; #endif for (int j = 0; j < tStates.size(); j++) { @@ -913,7 +1035,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& } #if VERBOSE - std::cout << "States to enter: "; + std::cout << _name << ": States to enter: "; for (int i = 0; i < statesToEnter.size(); i++) { std::cout << LOCALNAME(statesToEnter[i]) << ":" << ATTR(statesToEnter[i], "id") << ", "; } @@ -924,7 +1046,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& NodeSet ancestors = getProperAncestors(tStates[j], ancestor); #if VERBOSE - std::cout << "Proper Ancestors of " << ATTR(tStates[j], "id") << " and " << ATTR(ancestor, "id") << ": "; + std::cout << _name << ": Proper Ancestors of " << ATTR(tStates[j], "id") << " and " << ATTR(ancestor, "id") << ": "; for (int i = 0; i < ancestors.size(); i++) { std::cout << ATTR(ancestors[i], "id") << ", "; } @@ -955,7 +1077,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& statesToEnter.to_document_order(); #if VERBOSE - std::cout << "States to enter: "; + std::cout << _name << ": States to enter: "; for (int i = 0; i < statesToEnter.size(); i++) { std::cout << ATTR(statesToEnter[i], "id") << ", "; } diff --git a/src/uscxml/interpreter/InterpreterDraft6.h b/src/uscxml/interpreter/InterpreterDraft6.h index 7550124..a958b47 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.h +++ b/src/uscxml/interpreter/InterpreterDraft6.h @@ -27,7 +27,8 @@ namespace uscxml { class InterpreterDraft6 : public InterpreterImpl { protected: void interpret(); - void mainEventLoop(); + InterpreterState step(bool blocking); + void stabilize(); void microstep(const Arabica::XPath::NodeSet& enabledTransitions); void enterStates(const Arabica::XPath::NodeSet& enabledTransitions); @@ -44,6 +45,8 @@ protected: bool isPreemptingTransition(const Arabica::DOM::Node& t1, const Arabica::DOM::Node& t2); bool isEnabledTransition(const Arabica::DOM::Node& transition, const std::string& event); + Arabica::XPath::NodeSet getDocumentInitialTransitions(); + bool isCrossingBounds(const Arabica::DOM::Node& transition); bool isWithinParallel(const Arabica::DOM::Node& transition); Arabica::DOM::Node findLCPA(const Arabica::XPath::NodeSet& states); diff --git a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp index 3130d42..a064394 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp @@ -230,7 +230,7 @@ void JSCDataModel::setEvent(const Event& event) { handleException(exception); } else { JSStringRef propName = JSStringCreateWithUTF8CString("data"); - JSStringRef contentStr = JSStringCreateWithUTF8CString(Interpreter::spaceNormalize(event.content).c_str()); + JSStringRef contentStr = JSStringCreateWithUTF8CString(InterpreterImpl::spaceNormalize(event.content).c_str()); JSObjectSetProperty(_ctx, eventObj, propName, JSValueMakeString(_ctx, contentStr), 0, &exception); JSStringRelease(propName); JSStringRelease(contentStr); @@ -566,7 +566,7 @@ void JSCDataModel::assign(const Element& assignElem, try { evalAsValue(key + " = " + content); } catch (...) { - evalAsValue(key + " = " + "\"" + Interpreter::spaceNormalize(content) + "\""); + evalAsValue(key + " = " + "\"" + InterpreterImpl::spaceNormalize(content) + "\""); } } else { JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), JSStringCreateWithUTF8CString(key.c_str()), JSValueMakeUndefined(_ctx), 0, &exception); diff --git a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp index bddea83..ec9557e 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp @@ -236,7 +236,7 @@ void V8DataModel::setEvent(const Event& event) { if (json) { eventObj->Set(v8::String::New("data"), getDataAsValue(json)); } else { - eventObj->Set(v8::String::New("data"), v8::String::New(Interpreter::spaceNormalize(event.content).c_str())); + eventObj->Set(v8::String::New("data"), v8::String::New(InterpreterImpl::spaceNormalize(event.content).c_str())); } } else { // _event.data is KVP @@ -603,7 +603,7 @@ void V8DataModel::assign(const Element& assignElem, try { evalAsValue(key + " = " + content); } catch (...) { - evalAsValue(key + " = " + "\"" + Interpreter::spaceNormalize(content) + "\""); + evalAsValue(key + " = " + "\"" + InterpreterImpl::spaceNormalize(content) + "\""); } } else { global->Set(v8::String::New(key.c_str()), v8::Undefined()); diff --git a/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp b/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp index 0ad6030..4cbe8f5 100644 --- a/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp +++ b/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp @@ -275,7 +275,7 @@ void SWIDataModel::setEvent(const Event& event) { dataInitStr << "load_xml_file('" << domUrl.asLocalFile(".pl") << "', XML), copy_term(XML,DATA), assert(event(data(DATA)))"; PlCall(dataInitStr.str().c_str()); } else if (event.content.size() > 0) { - PlCall("assert", PlCompound("event", PlCompound("data", PlString(Interpreter::spaceNormalize(event.content).c_str())))); + PlCall("assert", PlCompound("event", PlCompound("data", PlString(InterpreterImpl::spaceNormalize(event.content).c_str())))); } else if (event.data) { assertFromData(event.data, "event(data(", 2); } diff --git a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp index bf32bf9..4d9854b 100644 --- a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp +++ b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp @@ -195,7 +195,7 @@ void XPathDataModel::setEvent(const Event& event) { } if (event.content.size() > 0) { - Text textNode = _doc.createTextNode(Interpreter::spaceNormalize(event.content).c_str()); + Text textNode = _doc.createTextNode(InterpreterImpl::spaceNormalize(event.content).c_str()); eventDataElem.appendChild(textNode); } if (event.dom) { @@ -501,7 +501,7 @@ void XPathDataModel::assign(const Element& assignElem, } assign(key, nodeSet, assignElem); } else if (content.length() > 0) { - Text textNode = _doc.createTextNode(Interpreter::spaceNormalize(content)); + Text textNode = _doc.createTextNode(InterpreterImpl::spaceNormalize(content)); nodeSet.push_back(textNode); assign(key, nodeSet, assignElem); } else if (HAS_ATTR(assignElem, "expr")) { diff --git a/src/uscxml/plugins/element/respond/RespondElement.cpp b/src/uscxml/plugins/element/respond/RespondElement.cpp index f415b19..4fe0d2e 100644 --- a/src/uscxml/plugins/element/respond/RespondElement.cpp +++ b/src/uscxml/plugins/element/respond/RespondElement.cpp @@ -78,7 +78,7 @@ void RespondElement::enterElement(const Arabica::DOM::Node& node) { httpReply.status = strTo(statusStr);; // extract the content - Arabica::XPath::NodeSet contents = Interpreter::filterChildElements(_interpreter->getXMLPrefixForNS(getNamespace()) + "content", node); + Arabica::XPath::NodeSet contents = InterpreterImpl::filterChildElements(_interpreter->getXMLPrefixForNS(getNamespace()) + "content", node); if (contents.size() > 0) { if (HAS_ATTR(contents[0], "expr")) { // -- content is evaluated string from datamodel ------ if (_interpreter->getDataModel()) { @@ -141,7 +141,7 @@ void RespondElement::enterElement(const Arabica::DOM::Node& node) { } // process headers - Arabica::XPath::NodeSet headers = Interpreter::filterChildElements(_interpreter->getXMLPrefixForNS(getNamespace()) + "header", node); + Arabica::XPath::NodeSet headers = InterpreterImpl::filterChildElements(_interpreter->getXMLPrefixForNS(getNamespace()) + "header", node); for (int i = 0; i < headers.size(); i++) { std::string name; if (HAS_ATTR(headers[i], "name")) { diff --git a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp index d89d8ac..f232e52 100644 --- a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp +++ b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp @@ -43,7 +43,8 @@ USCXMLInvoker::~USCXMLInvoker() { _cancelled = true; Event event; event.name = "unblock.and.die"; - _invokedInterpreter.receive(event); + if (_invokedInterpreter) + _invokedInterpreter.receive(event); }; boost::shared_ptr USCXMLInvoker::create(InterpreterImpl* interpreter) { @@ -84,6 +85,7 @@ void USCXMLInvoker::invoke(const InvokeRequest& req) { if (_invokedInterpreter) { DataModel dataModel(_invokedInterpreter.getImpl()->getDataModel()); _invokedInterpreter.getImpl()->setParentQueue(&_parentQueue); + // transfer namespace prefixes _invokedInterpreter.setNameSpaceInfo(_parentInterpreter->getNameSpaceInfo()); _invokedInterpreter.getImpl()->_sessionId = req.invokeid; @@ -92,7 +94,6 @@ void USCXMLInvoker::invoke(const InvokeRequest& req) { _invokedInterpreter.getImpl()->setInvokeRequest(req); _invokedInterpreter.start(); -// tthread::this_thread::sleep_for(tthread::chrono::seconds(1)); } else { /// test 530 _parentInterpreter->receive(Event("done.invoke." + _invokeId, Event::PLATFORM)); diff --git a/src/uscxml/transform/ChartToFSM.cpp b/src/uscxml/transform/ChartToFSM.cpp index 8b1725c..7f38397 100644 --- a/src/uscxml/transform/ChartToFSM.cpp +++ b/src/uscxml/transform/ChartToFSM.cpp @@ -902,7 +902,7 @@ GlobalState::GlobalState(const Arabica::XPath::NodeSet& activeState std::ostringstream idSS; idSS << "active-"; for (int i = 0; i < activeStates.size(); i++) { - if (!Interpreter::isFinal(activeStates[i])) + if (!InterpreterImpl::isFinal(activeStates[i])) isFinal = false; idSS << ATTR(activeStates[i], "id") << "-"; } @@ -1043,7 +1043,7 @@ std::list GlobalTransition::getCommonEvents(const NodeSet eventNames = Interpreter::tokenizeIdRefs(ATTR(transitions[i], "event")); + std::list eventNames = InterpreterImpl::tokenizeIdRefs(ATTR(transitions[i], "event")); for (std::list::iterator eventNameIter = eventNames.begin(); eventNameIter != eventNames.end(); @@ -1062,7 +1062,7 @@ std::list GlobalTransition::getCommonEvents(const NodeSet GlobalTransition::getCommonEvents(const NodeSet::iterator innerEventNameIter = prefixes.begin(); innerEventNameIter != prefixes.end(); innerEventNameIter++) { - if (!iequals(*outerEventNameIter, *innerEventNameIter) && Interpreter::nameMatch(*outerEventNameIter, *innerEventNameIter)) { + if (!iequals(*outerEventNameIter, *innerEventNameIter) && InterpreterImpl::nameMatch(*outerEventNameIter, *innerEventNameIter)) { goto IS_PREFIX; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 18dfc03..49169fc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,6 +62,11 @@ target_link_libraries(test-arabica-namespaces uscxml) add_test(test-arabica-namespaces ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-arabica-namespaces ${CMAKE_SOURCE_DIR}/test) set_target_properties(test-arabica-namespaces PROPERTIES FOLDER "Tests") +add_executable(test-lifecycle src/test-lifecycle.cpp) +target_link_libraries(test-lifecycle uscxml) +add_test(test-lifecycle ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-arabica-namespaces) +set_target_properties(test-lifecycle PROPERTIES FOLDER "Tests") + if (NOT WIN32) add_executable(test-arabica-events src/test-arabica-events.cpp) target_link_libraries(test-arabica-events uscxml) diff --git a/test/src/test-arabica-namespaces.cpp b/test/src/test-arabica-namespaces.cpp index ae529c9..aa7a5b4 100644 --- a/test/src/test-arabica-namespaces.cpp +++ b/test/src/test-arabica-namespaces.cpp @@ -97,7 +97,7 @@ static void validateRootFoo(std::pair, NameSpaceInfo>& par assert(TAGNAME(root) == nsInfo.xmlNSPrefix + "root"); assert(LOCALNAME(root) == "root"); - NodeSet foosFiltered = Interpreter::filterChildElements(nsInfo.xmlNSPrefix + "foo", root); + NodeSet foosFiltered = InterpreterImpl::filterChildElements(nsInfo.xmlNSPrefix + "foo", root); assert(foosFiltered.size() == 3); NodeSet foosXPath = _xpath.evaluate("//" + nsInfo.xpathPrefix + "foo", root).asNodeSet(); assert(foosXPath.size() == 3); @@ -119,7 +119,7 @@ static void validateRootFooBar(std::pair, NameSpaceInfo>& Node root = document.getDocumentElement(); _xpath.setNamespaceContext(*nsInfo.getNSContext()); - NodeSet barsFiltered = Interpreter::filterChildElements(nsInfo.xmlNSPrefix + "bar", root); + NodeSet barsFiltered = InterpreterImpl::filterChildElements(nsInfo.xmlNSPrefix + "bar", root); assert(barsFiltered.size() == 3); NodeSet barsXPath = _xpath.evaluate("//" + nsInfo.xpathPrefix + "bar", root).asNodeSet(); assert(barsXPath.size() == 3); @@ -144,7 +144,7 @@ static void validateRootFooBarBaz(std::pair, NameSpaceInfo assert(TAGNAME(root) == nsInfo.xmlNSPrefix + "root"); assert(LOCALNAME(root) == "root"); - NodeSet bazsFiltered = Interpreter::filterChildElements(nsInfo.xmlNSPrefix + "baz", root); + NodeSet bazsFiltered = InterpreterImpl::filterChildElements(nsInfo.xmlNSPrefix + "baz", root); assert(bazsFiltered.size() == 3); NodeSet bazsXPath = _xpath.evaluate("//" + nsInfo.xpathPrefix + "baz", root).asNodeSet(); assert(bazsXPath.size() == 3); diff --git a/test/src/test-lifecycle.cpp b/test/src/test-lifecycle.cpp new file mode 100644 index 0000000..682e796 --- /dev/null +++ b/test/src/test-lifecycle.cpp @@ -0,0 +1,129 @@ +#include "uscxml/config.h" +#include "uscxml/Interpreter.h" +#include + +#include "uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.h" +#include + +#ifdef HAS_SIGNAL_H +#include +#endif + +#ifdef HAS_EXECINFO_H +#include +#endif + +#ifdef HAS_DLFCN_H +#include +#endif + +#ifdef _WIN32 +#include "XGetopt.h" +#endif + +int startedAt; +int lastTransitionAt; + +#ifdef HAS_EXECINFO_H +void printBacktrace(void** array, int size) { + char** messages = backtrace_symbols(array, size); + for (int i = 0; i < size && messages != NULL; ++i) { + std::cerr << "\t" << messages[i] << std::endl; + } + std::cerr << std::endl; + free(messages); +} + +#ifdef HAS_DLFCN_H +// see https://gist.github.com/nkuln/2020860 +typedef void (*cxa_throw_type)(void *, void *, void (*) (void *)); +cxa_throw_type orig_cxa_throw = 0; + +void load_orig_throw_code() { + orig_cxa_throw = (cxa_throw_type) dlsym(RTLD_NEXT, "__cxa_throw"); +} + +extern "C" +void __cxa_throw (void *thrown_exception, void *pvtinfo, void (*dest)(void *)) { + std::cerr << __FUNCTION__ << " will throw exception from " << std::endl; + if (orig_cxa_throw == 0) + load_orig_throw_code(); + + void *array[50]; + size_t size = backtrace(array, 50); + printBacktrace(array, size); + orig_cxa_throw(thrown_exception, pvtinfo, dest); +} +#endif +#endif + + +// see http://stackoverflow.com/questions/2443135/how-do-i-find-where-an-exception-was-thrown-in-c +void customTerminate() { + static bool tried_throw = false; + try { + // try once to re-throw currently active exception + if (!tried_throw) { + throw; + tried_throw = true; + } else { + tried_throw = false; + }; + } catch (const std::exception &e) { + std::cerr << __FUNCTION__ << " caught unhandled exception. what(): " + << e.what() << std::endl; + } catch (const uscxml::Event &e) { + std::cerr << __FUNCTION__ << " caught unhandled exception. Event: " + << e << std::endl; + } catch (...) { + std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." + << std::endl; + } + +#ifdef HAS_EXECINFO_H + void * array[50]; + int size = backtrace(array, 50); + + printBacktrace(array, size); +#endif + abort(); +} +int main(int argc, char** argv) { + using namespace uscxml; + + std::set_terminate(customTerminate); + +#if defined(HAS_SIGNAL_H) && !defined(WIN32) + signal(SIGPIPE, SIG_IGN); +#endif + + google::InitGoogleLogging(argv[0]); + google::LogToStderr(); + + Interpreter interpreter = Interpreter::fromURI("/Users/sradomski/Documents/TK/Code/uscxml/test/w3c/ecma/test530.scxml"); + InterpreterState state; + do { + state = interpreter.step(true); + switch (state) { + case uscxml::FINISHED: + std::cout << "FINISHED" << std::endl; + break; + case uscxml::INIT_FAILED: + std::cout << "INIT_FAILED" << std::endl; + break; + case uscxml::NOTHING_TODO: + std::cout << "NOTHING_TODO" << std::endl; + break; + case uscxml::INTERRUPTED: + std::cout << "INTERRUPTED" << std::endl; + break; + case uscxml::PROCESSED: + std::cout << "PROCESSED" << std::endl; + break; + default: + break; + } + } while(state != FINISHED); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/test/src/test-predicates.cpp b/test/src/test-predicates.cpp index ed155b4..00be408 100644 --- a/test/src/test-predicates.cpp +++ b/test/src/test-predicates.cpp @@ -16,24 +16,24 @@ int main(int argc, char** argv) { assert(interpreter); Node atomicState = interpreter.getState("atomic"); - assert(Interpreter::isAtomic(atomicState)); - assert(!Interpreter::isParallel(atomicState)); - assert(!Interpreter::isCompound(atomicState)); + assert(InterpreterImpl::isAtomic(atomicState)); + assert(!InterpreterImpl::isParallel(atomicState)); + assert(!InterpreterImpl::isCompound(atomicState)); Node compoundState = interpreter.getState("compound"); - assert(!Interpreter::isAtomic(compoundState)); - assert(!Interpreter::isParallel(compoundState)); - assert(Interpreter::isCompound(compoundState)); + assert(!InterpreterImpl::isAtomic(compoundState)); + assert(!InterpreterImpl::isParallel(compoundState)); + assert(InterpreterImpl::isCompound(compoundState)); Node parallelState = interpreter.getState("parallel"); - assert(!Interpreter::isAtomic(parallelState)); - assert(Interpreter::isParallel(parallelState)); - assert(!Interpreter::isCompound(parallelState)); // parallel states are not compound! + assert(!InterpreterImpl::isAtomic(parallelState)); + assert(InterpreterImpl::isParallel(parallelState)); + assert(!InterpreterImpl::isCompound(parallelState)); // parallel states are not compound! - NodeSet initialState = interpreter.getInitialStates(); + NodeSet initialState = interpreter.getImpl()->getInitialStates(); assert(initialState[0] == atomicState); - NodeSet childs = interpreter.getChildStates(compoundState); + NodeSet childs = interpreter.getImpl()->getChildStates(compoundState); Node compoundChild1 = interpreter.getState("compoundChild1"); Node compoundChild2 = interpreter.getState("compoundChild2"); assert(childs.size() > 0); @@ -41,39 +41,39 @@ int main(int argc, char** argv) { assert(Interpreter::isMember(compoundChild2, childs)); assert(!Interpreter::isMember(compoundState, childs)); - assert(Interpreter::isDescendant(compoundChild1, compoundState)); + assert(InterpreterImpl::isDescendant(compoundChild1, compoundState)); { std::string idrefs("id1"); - std::list tokenizedIdrefs = Interpreter::tokenizeIdRefs(idrefs); + std::list tokenizedIdrefs = InterpreterImpl::tokenizeIdRefs(idrefs); assert(tokenizedIdrefs.size() == 1); assert(tokenizedIdrefs.front().compare("id1") == 0); } { std::string idrefs(" id1"); - std::list tokenizedIdrefs = Interpreter::tokenizeIdRefs(idrefs); + std::list tokenizedIdrefs = InterpreterImpl::tokenizeIdRefs(idrefs); assert(tokenizedIdrefs.size() == 1); assert(tokenizedIdrefs.front().compare("id1") == 0); } { std::string idrefs(" id1 "); - std::list tokenizedIdrefs = Interpreter::tokenizeIdRefs(idrefs); + std::list tokenizedIdrefs = InterpreterImpl::tokenizeIdRefs(idrefs); assert(tokenizedIdrefs.size() == 1); assert(tokenizedIdrefs.front().compare("id1") == 0); } { std::string idrefs(" \tid1\n "); - std::list tokenizedIdrefs = Interpreter::tokenizeIdRefs(idrefs); + std::list tokenizedIdrefs = InterpreterImpl::tokenizeIdRefs(idrefs); assert(tokenizedIdrefs.size() == 1); assert(tokenizedIdrefs.front().compare("id1") == 0); } { std::string idrefs("id1 id2 id3"); - std::list tokenizedIdrefs = Interpreter::tokenizeIdRefs(idrefs); + std::list tokenizedIdrefs = InterpreterImpl::tokenizeIdRefs(idrefs); assert(tokenizedIdrefs.size() == 3); assert(tokenizedIdrefs.front().compare("id1") == 0); tokenizedIdrefs.pop_front(); @@ -84,7 +84,7 @@ int main(int argc, char** argv) { { std::string idrefs("\t id1 \nid2\n\n id3\t"); - std::list tokenizedIdrefs = Interpreter::tokenizeIdRefs(idrefs); + std::list tokenizedIdrefs = InterpreterImpl::tokenizeIdRefs(idrefs); assert(tokenizedIdrefs.size() == 3); assert(tokenizedIdrefs.front().compare("id1") == 0); tokenizedIdrefs.pop_front(); @@ -95,7 +95,7 @@ int main(int argc, char** argv) { { std::string idrefs("id1 \nid2 \tid3"); - std::list tokenizedIdrefs = Interpreter::tokenizeIdRefs(idrefs); + std::list tokenizedIdrefs = InterpreterImpl::tokenizeIdRefs(idrefs); assert(tokenizedIdrefs.size() == 3); assert(tokenizedIdrefs.front().compare("id1") == 0); tokenizedIdrefs.pop_front(); -- cgit v0.12