diff options
Diffstat (limited to 'src/uscxml/interpreter')
-rw-r--r-- | src/uscxml/interpreter/ContentExecutorImpl.cpp | 649 | ||||
-rw-r--r-- | src/uscxml/interpreter/ContentExecutorImpl.h | 143 | ||||
-rw-r--r-- | src/uscxml/interpreter/EventQueueImpl.cpp | 189 | ||||
-rw-r--r-- | src/uscxml/interpreter/EventQueueImpl.h | 125 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterDraft6.cpp | 573 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterDraft6.h | 56 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterFast.cpp | 42 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterFast.h | 47 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterImpl.cpp | 361 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterImpl.h | 290 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterMonitor.h | 95 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterRC.cpp | 661 | ||||
-rw-r--r-- | src/uscxml/interpreter/InterpreterRC.h | 68 | ||||
-rw-r--r-- | src/uscxml/interpreter/MicroStepFast.cpp | 1149 | ||||
-rw-r--r-- | src/uscxml/interpreter/MicroStepFast.h | 127 | ||||
-rw-r--r-- | src/uscxml/interpreter/MicroStepImpl.h | 127 |
16 files changed, 3255 insertions, 1447 deletions
diff --git a/src/uscxml/interpreter/ContentExecutorImpl.cpp b/src/uscxml/interpreter/ContentExecutorImpl.cpp new file mode 100644 index 0000000..6a06bec --- /dev/null +++ b/src/uscxml/interpreter/ContentExecutorImpl.cpp @@ -0,0 +1,649 @@ +/** + * @file + * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#include "ContentExecutorImpl.h" +#include "uscxml/util/String.h" +#include "uscxml/util/Predicates.h" +#include "uscxml/util/UUID.h" +#include "uscxml/util/URL.h" +#include "uscxml/messages/Data.h" + +#include <xercesc/parsers/XercesDOMParser.hpp> +#include <xercesc/sax/HandlerBase.hpp> +#include <xercesc/framework/MemBufInputSource.hpp> + +#include "easylogging++.h" + +namespace uscxml { + +using namespace xercesc; + +void BasicContentExecutorImpl::processRaise(xercesc::DOMElement* content) { + Event raised(ATTR(content, "event")); + _callbacks->enqueueInternal(raised); +} + +void BasicContentExecutorImpl::processSend(xercesc::DOMElement* element) { + Event sendEvent; + std::string target; + std::string type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; // default + uint32_t delayMs = 0; + + // test 331 + sendEvent.eventType = Event::EXTERNAL; + + // test 228 + std::string invokeId = _callbacks->getInvokeId(); + if (invokeId.size() > 0) { + sendEvent.invokeid = invokeId; + } + + try { + // event + if (HAS_ATTR(element, "eventexpr")) { + sendEvent.name = _callbacks->evalAsData(ATTR(element, "eventexpr")).atom; + } else if (HAS_ATTR(element, "event")) { + sendEvent.name = ATTR(element, "event"); + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element eventexpr", element); + } + + try { + // target + if (HAS_ATTR(element, "targetexpr")) { + target = _callbacks->evalAsData(ATTR(element, "targetexpr")).atom; + } else if (HAS_ATTR(element, "target")) { + target = ATTR(element, "target"); + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element targetexpr", element); + } + + try { + // type + if (HAS_ATTR(element, "typeexpr")) { + type = _callbacks->evalAsData(ATTR(element, "typeexpr")).atom; + } else if (HAS_ATTR(element, "type")) { + type = ATTR(element, "type"); + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element typeexpr", element); + } + + try { + // id + if (HAS_ATTR(element, "id")) { + sendEvent.sendid = ATTR(element, "id"); + } else { + /* + * The ids for <send> and <invoke> are subtly different. In a conformant + * SCXML document, they must be unique within the session, but in the case + * where the author does not provide them, the processor must generate a + * new unique ID not at load time but each time the element is executed. + * Furthermore the attribute 'idlocation' can be used to capture this + * automatically generated id. Finally note that the automatically generated + * id for <invoke> has a special format. See 6.4.1 Attribute Details for + * details. The SCXML processor may generate all other ids in any format, + * as long as they are unique. + */ + + /** + * + * If 'idlocation' is present, the SCXML Processor must generate an id when + * the parent <send> element is evaluated and store it in this location. + * See 3.14 IDs for details. + * + */ + sendEvent.sendid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); + if (HAS_ATTR(element, "idlocation")) { + _callbacks->assign(ATTR(element, "idlocation"), Data(sendEvent.sendid, Data::VERBATIM)); + } else { + sendEvent.hideSendId = true; + } + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element idlocation", element); + } + + try { + // delay + std::string delay; + if (HAS_ATTR(element, "delayexpr")) { + delay = _callbacks->evalAsData(ATTR(element, "delayexpr")); + } else if (HAS_ATTR(element, "delay")) { + delay = ATTR(element, "delay"); + } + if (delay.size() > 0) { + NumAttr delayAttr(delay); + if (iequals(delayAttr.unit, "ms")) { + delayMs = strTo<uint32_t>(delayAttr.value); + } else if (iequals(delayAttr.unit, "s")) { + delayMs = strTo<double>(delayAttr.value) * 1000; + } else if (delayAttr.unit.length() == 0) { // unit less delay is interpreted as milliseconds + delayMs = strTo<uint32_t>(delayAttr.value); + } else { + LOG(ERROR) << "Cannot make sense of delay value " << delay << ": does not end in 's' or 'ms'"; + } + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element delayexpr", element); + } + + try { + // namelist + processNameLists(sendEvent.namelist, element); + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element namelist", element); + } + + + try { + // params + processParams(sendEvent.params, element); + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element param expr", element); + } + + try { + // content + std::list<DOMElement*> contents = DOMUtils::filterChildElements(XML_PREFIX(element).str() + "content", element); + if (contents.size() > 0) { + sendEvent.data = elementAsData(contents.front()); + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element content", element); + } + + // if (sendReq->dom) { + // std::stringstream ss; + // ss << sendReq->dom; + // sendReq->xml = ss.str(); + // _dataModel.replaceExpressions(sendReq->xml); + // } + + // assert(_sendIds.find(sendReq->sendid) == _sendIds.end()); + // _sendIds[sendReq->sendid] = std::make_pair(this, sendReq); + + try { + _callbacks->checkValidSendType(type, target); + } catch (ErrorEvent e) { + e.data.compound["xpath"] = uscxml::Data(DOMUtils::xPathForNode(element), uscxml::Data::VERBATIM); + // test 332 + e.sendid = sendEvent.sendid; + throw e; + } + _callbacks->enqueue(type, target, delayMs, sendEvent); + +} + +void BasicContentExecutorImpl::processCancel(xercesc::DOMElement* content) { + std::string sendid; + if (HAS_ATTR(content, "sendid")) { + sendid = ATTR(content, "sendid"); + } else if (HAS_ATTR(content, "sendidexpr")) { + sendid = _callbacks->evalAsData(ATTR(content, "sendidexpr")).atom; + } else { + ERROR_EXECUTION_THROW2("Cancel element has neither sendid nor sendidexpr attribute", content); + + } + _callbacks->cancelDelayed(sendid); +} + +void BasicContentExecutorImpl::processIf(xercesc::DOMElement* content) { + bool blockIsTrue = _callbacks->isTrue(ATTR(content, "cond")); + + DOMNodeList* children = content->getChildNodes(); + for (unsigned int i = 0; i < children->getLength(); i++) { + if (children->item(i)->getNodeType() != DOMNode::ELEMENT_NODE) + continue; + + DOMElement* childElem = dynamic_cast<DOMElement*>(children->item(i)); + + if (iequals(TAGNAME(childElem), XML_PREFIX(content).str() + "elseif")) { + if (blockIsTrue) { + // last block was true, break here + break; + } + blockIsTrue = _callbacks->isTrue(ATTR(childElem, "cond")); + continue; + } + if (iequals(TAGNAME(childElem), XML_PREFIX(content).str() + "else")) { + if (blockIsTrue) { + // last block was true, break here + break; + } + blockIsTrue = true; + continue; + } + + // current block is true + if (blockIsTrue) { + // we do now that the prefix of content is correct + process(childElem, XML_PREFIX(content)); + } + } +} + +void BasicContentExecutorImpl::processAssign(xercesc::DOMElement* content) { + std::string location = ATTR(content, "location"); + _callbacks->assign(location, elementAsData(content)); +} + +void BasicContentExecutorImpl::processForeach(xercesc::DOMElement* content) { + std::string array = ATTR(content, "array"); + std::string item = ATTR(content, "item"); + std::string index = (HAS_ATTR(content, "index") ? ATTR(content, "index") : ""); + + uint32_t iterations = 0; + iterations = _callbacks->getLength(array); + + for (uint32_t iteration = 0; iteration < iterations; iteration++) { + _callbacks->setForeach(item, array, index, iteration); + + DOMNodeList* children = content->getChildNodes(); + for (unsigned int i = 0; i < children->getLength(); i++) { + if (children->item(i)->getNodeType() != DOMNode::ELEMENT_NODE) + continue; + process(dynamic_cast<DOMElement*>(children->item(i)), XML_PREFIX(content)); + } + } +} + +void BasicContentExecutorImpl::processLog(xercesc::DOMElement* content) { + std::string label = ATTR(content, "label"); + std::string expr = ATTR(content, "expr"); + + Data d = _callbacks->evalAsData(expr); + if (label.size() > 0) { + std::cout << label << ": "; + } + std::cout << d << std::endl; +} + +void BasicContentExecutorImpl::processScript(xercesc::DOMElement* content) { + // download as necessary + std::string scriptContent(X(content->getTextContent())); + _callbacks->evalAsData(scriptContent); + +} + +void BasicContentExecutorImpl::process(xercesc::DOMElement* block, const X& xmlPrefix) { + std::string tagName = TAGNAME(block); + + + if (iequals(tagName, xmlPrefix.str() + "onentry") || + iequals(tagName, xmlPrefix.str() + "onexit") || + iequals(tagName, xmlPrefix.str() + "transition")) { + + DOMNodeList* children = block->getChildNodes(); + try { + for(auto i = 0; i < children->getLength(); i++) { + if (children->item(i)->getNodeType() != DOMNode::ELEMENT_NODE) + continue; + // process any child eleents + process(dynamic_cast<DOMElement*>(children->item(i)), xmlPrefix); + } + } catch (Event e) { + // there has been an error in an executable content block + // we do not care - parent scope has to handle it! + throw e; + } + return; + } + + if (iequals(tagName, xmlPrefix.str() + "finalize")) { + std::list<DOMNode*> childElems = DOMUtils::filterChildType(DOMNode::ELEMENT_NODE, block, false); + if(childElems.size() > 0) { + for(auto elemIter = childElems.begin(); elemIter != childElems.end(); elemIter++) { + process(static_cast<DOMElement*>(*elemIter), xmlPrefix); + } + } else { + // issue 67 - empty finalize element + DOMNode* parent = block->getParentNode(); + if (parent && parent->getNodeType() == DOMNode::ELEMENT_NODE) { + DOMElement* invokeElem = static_cast<DOMElement*>(parent); + if (iequals(X(invokeElem->getTagName()).str(), xmlPrefix.str() + "invoke")) { + // we are the empth finalize element of an invoke + // Specification 6.5.2: http://www.w3.org/TR/scxml/#N110EF + + const Event& event = _callbacks->getCurrentEvent(); + std::list<std::string> names = tokenize(ATTR(invokeElem, "namelist")); + for (std::list<std::string>::iterator nameIter = names.begin(); nameIter != names.end(); nameIter++) { + if (event.namelist.find(*nameIter) != event.namelist.end()) { + // scxml i/o proc keeps a dedicated namelist + _callbacks->assign(*nameIter, event.namelist.at(*nameIter)); + } else if (event.data.compound.find(*nameIter) != event.data.compound.end()) { + // this is where it would end up with non scxml i/o processors + _callbacks->assign(*nameIter, event.data.compound.at(*nameIter)); + } + } + } + } + + } + return; + } + + try { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeExecutingContent, block); + + if (false) { + } else if (iequals(tagName, xmlPrefix.str() + "raise")) { + processRaise(block); + } else if (iequals(tagName, xmlPrefix.str() + "send")) { + processSend(block); + } else if (iequals(tagName, xmlPrefix.str() + "cancel")) { + processCancel(block); + } else if (iequals(tagName, xmlPrefix.str() + "if")) { + processIf(block); + } else if (iequals(tagName, xmlPrefix.str() + "assign")) { + processAssign(block); + } else if (iequals(tagName, xmlPrefix.str() + "foreach")) { + processForeach(block); + } else if (iequals(tagName, xmlPrefix.str() + "log")) { + processLog(block); + } else if (iequals(tagName, xmlPrefix.str() + "script")) { + processScript(block); + } else { + LOG(ERROR) << tagName; + assert(false); + } + } catch (ErrorEvent exc) { + + Event e(exc); + _callbacks->enqueueInternal(e); + LOG(ERROR) << exc << std::endl; + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterExecutingContent, block); + + throw e; // will be catched in microstepper + + } + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterExecutingContent, block); + +} + +void BasicContentExecutorImpl::invoke(xercesc::DOMElement* element) { + std::string type; + std::string source; + bool autoForward = false; + Event invokeEvent; + + // type + if (HAS_ATTR(element, "typeexpr")) { + type = _callbacks->evalAsData(ATTR(element, "typeexpr")).atom; + } else if (HAS_ATTR(element, "type")) { + type = ATTR(element, "type"); + } else { + // test 422 + type = "http://www.w3.org/TR/scxml/"; + } + + // src + if (HAS_ATTR(element, "srcexpr")) { + source = _callbacks->evalAsData(ATTR(element, "srcexpr")).atom; + } else if (HAS_ATTR(element, "src")) { + source = ATTR(element, "src"); + } + if (source.length() > 0) { + // absolutize url + } + + // id + try { + if (HAS_ATTR(element, "id")) { + invokeEvent.invokeid = ATTR(element, "id"); + } else { + invokeEvent.invokeid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); + if (HAS_ATTR(element, "idlocation")) { + _callbacks->assign(ATTR(element, "idlocation"), Data(invokeEvent.invokeid, Data::VERBATIM)); + } + } + // we need the invokeid to uninvoke - TODO: This is leaking! + char* invokeId = (char*)malloc(invokeEvent.invokeid.size() + 1); + memcpy(invokeId, invokeEvent.invokeid.c_str(), invokeEvent.invokeid.size()); + invokeId[invokeEvent.invokeid.size()] = 0; + + element->setUserData(X("invokeid"), (void*)invokeId, NULL); + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in invoke element idlocation", element); + } + + try { + // namelist + processNameLists(invokeEvent.namelist, element); + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element namelist", element); + } + + + try { + // params + processParams(invokeEvent.params, element); + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element param expr", element); + } + + try { + // content + std::list<DOMElement*> contents = DOMUtils::filterChildElements(XML_PREFIX(element).str() + "content", element); + if (contents.size() > 0) { + Data d = elementAsData(contents.front()); + if (d.type == Data::INTERPRETED && d.atom.size() > 0) { + // immediately evaluate! + invokeEvent.data = _callbacks->evalAsData(d.atom); + } else { + invokeEvent.data = d; + } + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in send element content", element); + } + + // autoforward + if (HAS_ATTR(element, "autoforward")) { + if (iequals(ATTR(element, "autoforward"), "true")) { + autoForward = true; + } + } + + // finalize + DOMElement* finalize = NULL; + std::list<DOMElement*> finalizes = DOMUtils::filterChildElements(XML_PREFIX(element).str() + "finalize", element); + if (finalizes.size() > 0) { + finalize = finalizes.front(); + } + + USCXML_MONITOR_CALLBACK2(_callbacks->getMonitor(), beforeUninvoking, element, invokeEvent.invokeid); + _callbacks->invoke(type, source, autoForward, finalize, invokeEvent); + USCXML_MONITOR_CALLBACK2(_callbacks->getMonitor(), afterUninvoking, element, invokeEvent.invokeid); +} + +void BasicContentExecutorImpl::uninvoke(xercesc::DOMElement* invoke) { + // TODO: DANGER This is the real danger here + char* invokeId = (char*)invoke->getUserData(X("invokeid")); + assert(invokeId != NULL); + + USCXML_MONITOR_CALLBACK2(_callbacks->getMonitor(), beforeUninvoking, invoke, invokeId); + _callbacks->uninvoke(invokeId); + USCXML_MONITOR_CALLBACK2(_callbacks->getMonitor(), afterUninvoking, invoke, invokeId); + + free(invokeId); +} + +void BasicContentExecutorImpl::raiseDoneEvent(xercesc::DOMElement* state, xercesc::DOMElement* doneData) { + + Event doneEvent; + doneEvent.name = "done.state."; + doneEvent.name += HAS_ATTR(state, "id") ? ATTR(state, "id") : DOMUtils::idForNode(state); + + if (doneData != NULL) { + try { + try { + // namelist + processNameLists(doneEvent.namelist, doneData); + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in donedata element namelist", doneData); + } + + + try { + // params + processParams(doneEvent.params, doneData); + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in donedata element param expr", doneData); + } + + try { + // content + std::list<DOMElement*> contents = DOMUtils::filterChildElements(XML_PREFIX(doneData).str() + "content", doneData); + if (contents.size() > 0) { + doneEvent.data = elementAsData(contents.front()); + } + } catch (Event e) { + ERROR_EXECUTION_THROW2("Syntax error in donedata element content", doneData); + } + + } catch (ErrorEvent exc) { + // clean out data test488 (needed?) + doneEvent.data = Data(); + + Event e(exc); + _callbacks->enqueueInternal(e); + // std::cout << exc << std::endl; + // throw e; + } + } + + _callbacks->enqueueInternal(doneEvent); + +} + +void BasicContentExecutorImpl::processNameLists(std::map<std::string, Data>& nameMap, DOMElement* element) { + if (HAS_ATTR(element, "namelist")) { + std::list<std::string> names = tokenize(ATTR(element, "namelist")); + for (std::list<std::string>::const_iterator nameIter = names.begin(); nameIter != names.end(); nameIter++) { + nameMap[*nameIter] = _callbacks->evalAsData(*nameIter); + } + } +} + +void BasicContentExecutorImpl::processParams(std::multimap<std::string, Data>& paramMap, DOMElement* element) { + std::list<DOMElement*> params = DOMUtils::filterChildElements(XML_PREFIX(element).str() + "param", element); + for (auto paramIter = params.begin(); paramIter != params.end(); paramIter++) { + std::string name = ATTR(*paramIter, "name"); + Data d; + if (HAS_ATTR(*paramIter, "expr")) { + d = _callbacks->evalAsData(ATTR(*paramIter, "expr")); + } else if (HAS_ATTR(*paramIter, "location")) { + d = _callbacks->evalAsData(ATTR(*paramIter, "location")); + } else { + d = elementAsData(*paramIter); + } + paramMap.insert(make_pair(name, d)); + } +} + +Data BasicContentExecutorImpl::elementAsData(xercesc::DOMElement* element) { + if (HAS_ATTR(element, "expr")) { +// return _callbacks->evalAsData(ATTR(element, "expr")); + if (LOCALNAME(element) == "content") { + // test 528 + return _callbacks->evalAsData(ATTR(element, "expr")); + } else { + // test 326 + return Data(ATTR(element, "expr"), Data::INTERPRETED); + } + } + + if (HAS_ATTR(element, "src")) { + // remote content from URL + + // test 446, test 552, test 558 + std::string src = ATTR(element, "src"); + URL url(ATTR(element, "src")); + if (!url.isAbsolute()) { + url = URL::resolve(url, _callbacks->getBaseURL()); + } + + std::string content = url.getInContent(); + + // make an attempt to parse as XML + try { + xercesc::XercesDOMParser* parser = new xercesc::XercesDOMParser(); + parser->setValidationScheme(xercesc::XercesDOMParser::Val_Always); + parser->setDoNamespaces(true); + parser->useScanner(xercesc::XMLUni::fgWFXMLScanner); + + xercesc::ErrorHandler* errHandler = new xercesc::HandlerBase(); + parser->setErrorHandler(errHandler); + + std::string tmp = url; + xercesc::MemBufInputSource is((XMLByte*)content.c_str(), content.size(), X("fake")); + + parser->parse(is); + + Data d; + xercesc::DOMDocument* doc = parser->adoptDocument(); + d.adoptedDoc = std::make_shared<xercesc::DOMDocument*>(doc); + d.node = doc->getDocumentElement(); + return d; + + } catch (...) { + // just ignore and return as an interpreted string below + } + try { + Data d = _callbacks->getAsData(content); + if (!d.empty()) + return d; + } catch(...) {} + + return Data(spaceNormalize(content), Data::VERBATIM); + + } else { + // local content in document + + std::list<DOMNode*> elementChildren = DOMUtils::filterChildType(DOMNode::ELEMENT_NODE, element); + if (elementChildren.size() == 1) { + return Data(elementChildren.front()); + } else if (elementChildren.size() > 1) { + return Data(element); + } + + std::list<DOMNode*> textChildren = DOMUtils::filterChildType(DOMNode::TEXT_NODE, element); + if (textChildren.size() > 0) { + std::stringstream contentSS; + for (auto textIter = textChildren.begin(); textIter != textChildren.end(); textIter++) { + contentSS << X((*textIter)->getNodeValue()); + } + try { + Data d = _callbacks->getAsData(contentSS.str()); + if (!d.empty()) + return d; + } catch(...) {} + + return Data(spaceNormalize(contentSS.str()), Data::VERBATIM); + } + } + + LOG(WARNING) << "Element " << DOMUtils::xPathForNode(element) << " did not yield any data"; + return Data(); +} + +}
\ No newline at end of file diff --git a/src/uscxml/interpreter/ContentExecutorImpl.h b/src/uscxml/interpreter/ContentExecutorImpl.h new file mode 100644 index 0000000..c0d28a2 --- /dev/null +++ b/src/uscxml/interpreter/ContentExecutorImpl.h @@ -0,0 +1,143 @@ +/** + * @file + * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + + +#ifndef CONTENTEXECUTORIMPL_H_13F2884F +#define CONTENTEXECUTORIMPL_H_13F2884F + +#include "uscxml/Common.h" +#include "uscxml/util/DOM.h" +#include "uscxml/messages/Data.h" +#include "uscxml/messages/Event.h" +#include "uscxml/interpreter/InterpreterMonitor.h" +#include <xercesc/dom/DOM.hpp> +#include <string> + +namespace uscxml { + +class USCXML_API ContentExecutorCallbacks { +public: + virtual void enqueueInternal(const Event& event) = 0; + virtual void enqueueExternal(const Event& event) = 0; + virtual void enqueueExternalDelayed(const Event& event, size_t delayMs, const std::string& eventUUID) = 0; + virtual void cancelDelayed(const std::string& eventId) = 0; + + virtual bool isTrue(const std::string& expr) = 0; + virtual size_t getLength(const std::string& expr) = 0; + + virtual void setForeach(const std::string& item, + const std::string& array, + const std::string& index, + uint32_t iteration) = 0; + + virtual Data evalAsData(const std::string& expr) = 0; + virtual Data getAsData(const std::string& expr) = 0; + virtual void assign(const std::string& location, const Data& data) = 0; + + + virtual std::string getInvokeId() = 0; + virtual std::string getBaseURL() = 0; + virtual bool checkValidSendType(const std::string& type, const std::string& target) = 0; + virtual void enqueue(const std::string& type, const std::string& target, size_t delayMs, const Event& sendEvent) = 0; + virtual void invoke(const std::string& type, const std::string& src, bool autoForward, xercesc::DOMElement* finalize, const Event& invokeEvent) = 0; + virtual void uninvoke(const std::string& invokeId) = 0; + + virtual const Event& getCurrentEvent() = 0; + + /** Monitoring */ + virtual InterpreterMonitor* getMonitor() = 0; + +}; + +class USCXML_API ContentExecutorImpl { +public: + ContentExecutorImpl(ContentExecutorCallbacks* callbacks) : _callbacks(callbacks) {} + + virtual void process(xercesc::DOMElement* block, const X& xmlPrefix) = 0; + + virtual void invoke(xercesc::DOMElement* invoke) = 0; + virtual void uninvoke(xercesc::DOMElement* invoke) = 0; + + virtual void raiseDoneEvent(xercesc::DOMElement* state, xercesc::DOMElement* doneData) = 0; + virtual Data elementAsData(xercesc::DOMElement* element) = 0; + +protected: + ContentExecutorCallbacks* _callbacks; + +}; + +class USCXML_API BasicContentExecutorImpl : public ContentExecutorImpl { +public: + BasicContentExecutorImpl(ContentExecutorCallbacks* callbacks) : ContentExecutorImpl(callbacks) {} + virtual ~BasicContentExecutorImpl() {} + + void processRaise(xercesc::DOMElement* content); + void processSend(xercesc::DOMElement* element); + void processCancel(xercesc::DOMElement* content); + void processIf(xercesc::DOMElement* content); + void processAssign(xercesc::DOMElement* content); + void processForeach(xercesc::DOMElement* content); + void processLog(xercesc::DOMElement* content); + void processScript(xercesc::DOMElement* content); + + virtual void process(xercesc::DOMElement* block, const X& xmlPrefix); + + virtual void invoke(xercesc::DOMElement* invoke); + virtual void uninvoke(xercesc::DOMElement* invoke); + virtual void raiseDoneEvent(xercesc::DOMElement* state, xercesc::DOMElement* doneData); + + virtual Data elementAsData(xercesc::DOMElement* element); + +protected: + void processNameLists(std::map<std::string, Data>& nameMap, xercesc::DOMElement* element); + void processParams(std::multimap<std::string, Data>& paramMap, xercesc::DOMElement* element); + +}; + +class USCXML_API ContentExecutor { +public: + PIMPL_OPERATORS(ContentExecutor) + + virtual void process(xercesc::DOMElement* block, const X& xmlPrefix) { + _impl->process(block, xmlPrefix); + } + + virtual void invoke(xercesc::DOMElement* invoke) { + _impl->invoke(invoke); + } + + virtual void uninvoke(xercesc::DOMElement* invoke) { + _impl->uninvoke(invoke); + } + + virtual Data elementAsData(xercesc::DOMElement* element) { + return _impl->elementAsData(element); + } + + virtual void raiseDoneEvent(xercesc::DOMElement* state, xercesc::DOMElement* doneData) { + return _impl->raiseDoneEvent(state, doneData); + } + +protected: + std::shared_ptr<ContentExecutorImpl> _impl; +}; + +} + +#endif /* end of include guard: CONTENTEXECUTORIMPL_H_13F2884F */ diff --git a/src/uscxml/interpreter/EventQueueImpl.cpp b/src/uscxml/interpreter/EventQueueImpl.cpp new file mode 100644 index 0000000..345da69 --- /dev/null +++ b/src/uscxml/interpreter/EventQueueImpl.cpp @@ -0,0 +1,189 @@ +/** + * @file + * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#include "EventQueueImpl.h" +#include <event2/util.h> // for evutil_socket_t +#include <event2/thread.h> +#include <assert.h> + +#include <easylogging++.h> + +namespace uscxml { + +EventQueueImpl::EventQueueImpl() { +} +EventQueueImpl::~EventQueueImpl() { +} + +Event EventQueueImpl::dequeue(bool blocking) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + if (blocking) { + while (_queue.empty()) { + _cond.wait(_mutex); + } + } + if (_queue.size() > 0) { + Event event = _queue.front(); + _queue.pop_front(); +// LOG(ERROR) << event.name; + return event; + } + return Event(); + +} + +void EventQueueImpl::enqueue(const Event& event) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + _queue.push_back(event); + _cond.notify_all(); +} + +static void dummyCallback(evutil_socket_t fd, short what, void *arg) { + timeval tv; + tv.tv_sec = 365 * 24 * 3600; + tv.tv_usec = 0; + event *ev = *(event **)arg; + evtimer_add(ev, &tv); +} + +DelayedEventQueueImpl::DelayedEventQueueImpl(DelayedEventQueueCallbacks* callbacks) { + _callbacks = callbacks; +#ifndef _WIN32 + evthread_use_pthreads(); +#else + evthread_use_windows_threads(); +#endif + _eventLoop = event_base_new(); + + // see here: https://github.com/named-data/ndn.cxx/blob/master/scheduler/scheduler.cc + // and here: https://www.mail-archive.com/libevent-users@seul.org/msg01676.html + timeval tv; + tv.tv_sec = 365 * 24 * 3600; + tv.tv_usec = 0; + _dummyEvent = evtimer_new(_eventLoop, dummyCallback, &_dummyEvent); + evtimer_add(_dummyEvent, &tv); + + _thread = NULL; + _isStarted = false; + start(); +} + +DelayedEventQueueImpl::~DelayedEventQueueImpl() { + stop(); + evtimer_del(_dummyEvent); + event_free(_dummyEvent); + event_base_free(_eventLoop); +} + +void DelayedEventQueueImpl::timerCallback(evutil_socket_t fd, short what, void *arg) { + struct callbackData *data = (struct callbackData*)arg; + std::lock_guard<std::recursive_mutex> lock(data->eventQueue->_mutex); + + if (data->eventQueue->_callbackData.find(data->eventUUID) == data->eventQueue->_callbackData.end()) + return; + + event_free(data->event); + data->eventQueue->_callbacks->eventReady(data->userData, data->eventUUID); + data->eventQueue->_callbackData.erase(data->eventUUID); +} + +void DelayedEventQueueImpl::enqueueDelayed(const Event& event, size_t delayMs, const std::string& eventUUID) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + if(_callbackData.find(eventUUID) != _callbackData.end()) { + cancelDelayed(eventUUID); + } + + _callbackData[eventUUID].eventUUID = eventUUID; + _callbackData[eventUUID].userData = event; + _callbackData[eventUUID].eventQueue = this; + + struct timeval delay = {static_cast<int32_t>(delayMs / 1000), static_cast<int32_t>((delayMs % 1000) * 1000)}; + struct event* e = event_new(_eventLoop, -1, 0, timerCallback, &_callbackData[eventUUID]); + + _callbackData[eventUUID].event = e; + + event_add(e, &delay); +} + +void DelayedEventQueueImpl::cancelAllDelayed() { + std::lock_guard<std::recursive_mutex> lock(_mutex); + + while(_callbackData.size() > 0) { + std::pair<std::string, callbackData> item = *_callbackData.begin(); + Event data = item.second.userData; + event_del(item.second.event); + event_free(item.second.event); + _callbackData.erase(item.first); + } + +} + +void DelayedEventQueueImpl::cancelDelayed(const std::string& eventId) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + + if(_callbackData.find(eventId) != _callbackData.end()) { + event_del(_callbackData[eventId].event); + event_free(_callbackData[eventId].event); + _callbackData.erase(eventId); + } +} + +void DelayedEventQueueImpl::run(void* instance) { + DelayedEventQueueImpl* INSTANCE = (DelayedEventQueueImpl*)instance; + int result; + while(INSTANCE->_isStarted) { + /** + * EVLOOP_NO_EXIT_ON_EMPTY was removed in libevent2.1 - we are + * using the event in the far future approach to get blocking + * behavior back (see comments in contructor) + */ + + // #ifndef EVLOOP_NO_EXIT_ON_EMPTY +// result = event_base_dispatch(INSTANCE->_eventLoop); + // #else + // TODO: this is polling when no events are enqueued + result = event_base_loop(INSTANCE->_eventLoop, EVLOOP_ONCE); +// assert(false); // NON-BLOCKING?! + //#endif + (void)result; + } +} + +void DelayedEventQueueImpl::start() { + if (_isStarted) { + return; + } + _isStarted = true; + _thread = new std::thread(DelayedEventQueueImpl::run, this); +} + +void DelayedEventQueueImpl::stop() { + if (_isStarted) { + _isStarted = false; + event_base_loopbreak(_eventLoop); + cancelAllDelayed(); + } + if (_thread) { + _thread->join(); + delete _thread; + _thread = NULL; + } +} + +}
\ No newline at end of file diff --git a/src/uscxml/interpreter/EventQueueImpl.h b/src/uscxml/interpreter/EventQueueImpl.h new file mode 100644 index 0000000..10543c9 --- /dev/null +++ b/src/uscxml/interpreter/EventQueueImpl.h @@ -0,0 +1,125 @@ +/** + * @file + * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#ifndef EVENTSOURCE_H_775AB206 +#define EVENTSOURCE_H_775AB206 + +#include "uscxml/Common.h" +#include "uscxml/messages/Event.h" +#include <string> +#include <map> +#include <list> +#include <thread> +#include <mutex> +#include <condition_variable> + +#include <event2/event.h> + + +namespace uscxml { + +class USCXML_API EventQueueImpl { +public: + EventQueueImpl(); + virtual ~EventQueueImpl(); + virtual Event dequeue(bool blocking); + virtual void enqueue(const Event& event); + +protected: + std::list<Event> _queue; + std::recursive_mutex _mutex; + std::condition_variable_any _cond; + +}; + +class USCXML_API DelayedEventQueueCallbacks { +public: + virtual void eventReady(Event& event, const std::string& eventId) = 0; +}; + +class USCXML_API DelayedEventQueueImpl : public EventQueueImpl { +public: + DelayedEventQueueImpl(DelayedEventQueueCallbacks* callbacks); + virtual ~DelayedEventQueueImpl(); + virtual void enqueueDelayed(const Event& event, size_t delayMs, const std::string& eventUUID); + virtual void cancelDelayed(const std::string& eventId); + virtual void cancelAllDelayed(); + +protected: + struct callbackData { + Event userData; + std::string eventUUID; + bool persist; + struct event *event; + DelayedEventQueueImpl* eventQueue; + }; + + bool _isStarted; + std::thread* _thread; + + std::map<std::string, callbackData> _callbackData; + struct event_base* _eventLoop; + struct event* _dummyEvent; + + static void run(void* instance); + void start(); + void stop(); + + static void timerCallback(evutil_socket_t fd, short what, void *arg); + DelayedEventQueueCallbacks* _callbacks; +}; + +class USCXML_API EventQueue { +public: + PIMPL_OPERATORS(EventQueue) + + virtual Event dequeue(bool blocking) { + return _impl->dequeue(blocking); + } + virtual void enqueue(const Event& event) { + return _impl->enqueue(event); + } + +protected: + std::shared_ptr<EventQueueImpl> _impl; + +}; + +class USCXML_API DelayedEventQueue : public EventQueue { +public: + PIMPL_OPERATORS2(DelayedEventQueue, EventQueue) + + void enqueueDelayed(const Event& event, size_t delayMs, const std::string& eventUUID) { + _impl->enqueueDelayed(event, delayMs, eventUUID); + } + void cancelDelayed(const std::string& eventUUID) { + return _impl->cancelDelayed(eventUUID); + } + + void cancelAllDelayed() { + return _impl->cancelAllDelayed(); + } + +protected: + std::shared_ptr<DelayedEventQueueImpl> _impl; +}; + +} + +#endif /* end of include guard: EVENTSOURCE_H_775AB206 */ diff --git a/src/uscxml/interpreter/InterpreterDraft6.cpp b/src/uscxml/interpreter/InterpreterDraft6.cpp deleted file mode 100644 index 6084641..0000000 --- a/src/uscxml/interpreter/InterpreterDraft6.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/** - * @file - * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see <http://www.opensource.org/licenses/bsd-license>. - * @endcond - */ - -#include "InterpreterDraft6.h" -#include "uscxml/concurrency/DelayedEventQueue.h" - -#include <glog/logging.h> -#include "uscxml/UUID.h" -#include "uscxml/dom/DOMUtils.h" - -#define VERBOSE 0 - -namespace uscxml { - -using namespace Arabica::XPath; -using namespace Arabica::DOM; - -// see: http://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation - - -Arabica::XPath::NodeSet<std::string> InterpreterDraft6::removeConflictingTransitions(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { - Arabica::XPath::NodeSet<std::string> filteredTransitions; - for (unsigned int i = 0; i < enabledTransitions.size(); i++) { - Element<std::string> t(enabledTransitions[i]); - if (!isTargetless(t)) { - for (unsigned int j = 0; j < filteredTransitions.size(); j++) { - if (j == i) - continue; - Element<std::string> t2(filteredTransitions[j]); - if (isPreemptingTransition(t2, t)) { -#if 0 - std::cout << "#####" << std::endl << "Transition " << std::endl - << t2 << std::endl << " preempts " << std::endl << t << std::endl << "#####" << std::endl; -#endif - goto LOOP; - } - } - } -#if 0 - std::cout << "----" << "Enabling Transition " << std::endl << t << std::endl; -#endif - - filteredTransitions.push_back(t); -LOOP: - ; - } - return filteredTransitions; -} - - - -/** - * Is t1 preempting t2? - */ -bool InterpreterDraft6::isPreemptingTransition(const Element<std::string>& t1, const Element<std::string>& t2) { - assert(t1); - assert(t2); - -#if 0 - std::cout << "Checking preemption: " << std::endl << t1 << std::endl << t2 << std::endl; -#endif - - if (t1 == t2) - return false; - if (isTargetless(t1)) - return false; // targetless transitions do not preempt any other transitions - if (isWithinParallel(t1) && isCrossingBounds(t2)) - return true; // transitions within a single child of <parallel> preempt transitions that cross child boundaries - if (isCrossingBounds(t1)) - return true; // transitions that cross child boundaries preempt all other transitions - - return false; -} - -bool InterpreterDraft6::isCrossingBounds(const Element<std::string>& transition) { - if (!isTargetless(transition) && !isWithinParallel(transition)) - return true; - return false; -} - -bool InterpreterDraft6::isWithinParallel(const Element<std::string>& transition) { - if (isTargetless(transition)) - return false; - - if (_transWithinParallel.find(transition) != _transWithinParallel.end()) - return _transWithinParallel[transition]; - - Node<std::string> source; - if (HAS_ATTR(transition, "type") && iequals(ATTR(transition, "type"), "internal")) { - source = getSourceState(transition); - } else { - source = getSourceState(transition).getParentNode(); - } - NodeSet<std::string> targets = getTargetStates(transition); - targets.push_back(source); - - Node<std::string> lcpa = findLCPA(targets); - _transWithinParallel[transition] = lcpa; - - return _transWithinParallel[transition]; -} - -Node<std::string> InterpreterDraft6::findLCPA(const Arabica::XPath::NodeSet<std::string>& states) { - Arabica::XPath::NodeSet<std::string> ancestors = getProperAncestors(states[0], Node<std::string>()); - Node<std::string> ancestor; - for (size_t i = 0; i < ancestors.size(); i++) { - if (!isParallel(Element<std::string>(ancestors[i]))) - continue; - for (size_t j = 0; j < states.size(); j++) { - if (!isDescendant(states[j], ancestors[i]) && (states[j] != ancestors[i])) - goto NEXT_ANCESTOR; - } - ancestor = ancestors[i]; - break; -NEXT_ANCESTOR: - ; - } - return ancestor; -} - - - -void InterpreterDraft6::exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { - NodeSet<std::string> statesToExit; - -#if VERBOSE - std::cout << _name << ": Enabled exit transitions: " << std::endl; - for (size_t i = 0; i < enabledTransitions.size(); i++) { - std::cout << enabledTransitions[i] << std::endl; - } - std::cout << std::endl; -#endif - - for (size_t i = 0; i < enabledTransitions.size(); i++) { - Element<std::string> t = ((Element<std::string>)enabledTransitions[i]); - if (!isTargetless(t)) { - Node<std::string> ancestor; - Node<std::string> source = getSourceState(t); -// std::cout << t << std::endl << TAGNAME(t) << std::endl; - NodeSet<std::string> tStates = getTargetStates(t); - bool isInternal = (HAS_ATTR(t, "type") && iequals(ATTR(t, "type"), "internal")); // external is default - bool allDescendants = true; - for (size_t j = 0; j < tStates.size(); j++) { - if (!isDescendant(tStates[j], source)) { - allDescendants = false; - break; - } - } - if (isInternal && allDescendants && isCompound(Element<std::string>(source))) { - ancestor = source; - } else { - NodeSet<std::string> tmpStates; - tmpStates.push_back(source); - tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end()); -#if 0 - std::cout << _name << ": tmpStates: "; - for (size_t i = 0; i < tmpStates.size(); i++) { - std::cout << ATTR(tmpStates[i], "id") << ", "; - } - std::cout << std::endl; -#endif - ancestor = findLCCA(tmpStates); - } -#if 0 - std::cout << _name << ": Ancestor: " << ATTR(ancestor, "id") << std::endl;; -#endif - for (size_t j = 0; j < _configuration.size(); j++) { - if (isDescendant(_configuration[j], ancestor)) - statesToExit.push_back(_configuration[j]); - } - } - } - // remove statesToExit from _statesToInvoke - std::list<Node<std::string> > tmp; - for (size_t i = 0; i < _statesToInvoke.size(); i++) { - if (!isMember(_statesToInvoke[i], statesToExit)) { - tmp.push_back(_statesToInvoke[i]); - } - } - _statesToInvoke = NodeSet<std::string>(); - _statesToInvoke.insert(_statesToInvoke.end(), tmp.begin(), tmp.end()); - - statesToExit.forward(false); - statesToExit.sort(); - -#if 0 - std::cout << _name << ": States to exit: "; - for (size_t i = 0; i < statesToExit.size(); i++) { - std::cout << LOCALNAME(statesToExit[i]) << ":" << ATTR(statesToExit[i], "id") << ", "; - } - std::cout << std::endl; -#endif - - for (size_t i = 0; i < statesToExit.size(); i++) { - NodeSet<std::string> histories = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "history", statesToExit[i]); - for (size_t j = 0; j < histories.size(); j++) { - Element<std::string> historyElem = (Element<std::string>)histories[j]; - std::string historyType = (historyElem.hasAttribute("type") ? historyElem.getAttribute("type") : "shallow"); - NodeSet<std::string> historyNodes; - for (size_t k = 0; k < _configuration.size(); k++) { - if (iequals(historyType, "deep")) { - if (isAtomic(Element<std::string>(_configuration[k])) && isDescendant(_configuration[k], statesToExit[i])) - historyNodes.push_back(_configuration[k]); - } else { - if (_configuration[k].getParentNode() == statesToExit[i]) - historyNodes.push_back(_configuration[k]); - } - } - _historyValue[historyElem.getAttribute("id")] = historyNodes; -#if VERBOSE - std::cout << _name << ": History node " << ATTR(historyElem, "id") << " contains: "; - for (size_t i = 0; i < historyNodes.size(); i++) { - std::cout << ATTR_CAST(historyNodes[i], "id") << ", "; - } - std::cout << std::endl; -#endif - - } - } - - for (size_t i = 0; i < statesToExit.size(); i++) { - USCXML_MONITOR_CALLBACK3(beforeExitingState, Element<std::string>(statesToExit[i]), (i + 1 < statesToExit.size())) - - NodeSet<std::string> onExits = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "onExit", statesToExit[i]); - for (size_t j = 0; j < onExits.size(); j++) { - Element<std::string> onExitElem = (Element<std::string>)onExits[j]; - executeContent(onExitElem); - } - - USCXML_MONITOR_CALLBACK3(afterExitingState, Element<std::string>(statesToExit[i]), (i + 1 < statesToExit.size())) - - NodeSet<std::string> invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", statesToExit[i]); - for (size_t j = 0; j < invokes.size(); j++) { - Element<std::string> invokeElem = (Element<std::string>)invokes[j]; - if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { - // extension for flattened SCXML documents, we will need an explicit uninvoke element - } else { - cancelInvoke(invokeElem); - } - } - - // remove statesToExit[i] from _configuration - test409 - tmp.clear(); - for (size_t j = 0; j < _configuration.size(); j++) { - if (_configuration[j] != statesToExit[i]) { - tmp.push_back(_configuration[j]); - } - } - _configuration = NodeSet<std::string>(); - _configuration.insert(_configuration.end(), tmp.begin(), tmp.end()); - } -} - -void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { - NodeSet<std::string> statesToEnter; - NodeSet<std::string> statesForDefaultEntry; - // initialize the temporary table for default content in history states - NodeSet<std::string> defaultHistoryContent; - -#if VERBOSE - std::cout << _name << ": Enabled enter transitions: " << std::endl; - for (size_t i = 0; i < enabledTransitions.size(); i++) { - std::cout << "\t" << enabledTransitions[i] << std::endl; - } - std::cout << std::endl; -#endif - - for (size_t i = 0; i < enabledTransitions.size(); i++) { - Element<std::string> transition = ((Element<std::string>)enabledTransitions[i]); - if (!isTargetless(transition)) { - std::string transitionType = (iequals(transition.getAttribute("type"), "internal") ? "internal" : "external"); - NodeSet<std::string> tStates = getTargetStates(transition); - -#if VERBOSE - std::cout << _name << ": Target States: "; - for (size_t i = 0; i < tStates.size(); i++) { - std::cout << ATTR_CAST(tStates[i], "id") << ", "; - } - std::cout << std::endl; -#endif - - Node<std::string> ancestor; - Node<std::string> source = getSourceState(transition); -#if VERBOSE - std::cout << _name << ": Source States: " << ATTR_CAST(source, "id") << std::endl; -#endif - assert(source); - - bool allDescendants = true; - for (size_t j = 0; j < tStates.size(); j++) { - if (!isDescendant(tStates[j], source)) { - allDescendants = false; - break; - } - } - if (iequals(transitionType, "internal") && - isCompound(Element<std::string>(source)) && - allDescendants) { - ancestor = source; - } else { - NodeSet<std::string> tmpStates; - tmpStates.push_back(source); - tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end()); - - ancestor = findLCCA(tmpStates); - } - -#if VERBOSE - std::cout << _name << ": Ancestor: " << ATTR_CAST(ancestor, "id") << std::endl; -#endif - - for (size_t j = 0; j < tStates.size(); j++) { - addStatesToEnter(Element<std::string>(tStates[j]), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - -#if VERBOSE - std::cout << _name << ": States to enter: "; - for (size_t i = 0; i < statesToEnter.size(); i++) { - std::cout << LOCALNAME(statesToEnter[i]) << ":" << ATTR_CAST(statesToEnter[i], "id") << ", "; - } - std::cout << std::endl; -#endif - - for (size_t j = 0; j < tStates.size(); j++) { - NodeSet<std::string> ancestors = getProperAncestors(tStates[j], ancestor); - -#if VERBOSE - std::cout << _name << ": Proper Ancestors of " << ATTR_CAST(tStates[j], "id") << " and " << ATTR_CAST(ancestor, "id") << ": "; - for (size_t i = 0; i < ancestors.size(); i++) { - std::cout << ATTR_CAST(ancestors[i], "id") << ", "; - } - std::cout << std::endl; -#endif - - for (size_t k = 0; k < ancestors.size(); k++) { - statesToEnter.push_back(ancestors[k]); - if(isParallel(Element<std::string>(ancestors[k]))) { - NodeSet<std::string> childs = getChildStates(ancestors[k]); - for (size_t l = 0; l < childs.size(); l++) { - bool someIsDescendant = false; - for (size_t m = 0; m < statesToEnter.size(); m++) { - if (isDescendant(statesToEnter[m], childs[l])) { - someIsDescendant = true; - break; - } - } - if (!someIsDescendant) { - addStatesToEnter(Element<std::string>(childs[l]), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - } - } - } - } - } - } - statesToEnter.to_document_order(); - -#if VERBOSE - std::cout << _name << ": States to enter: "; - for (size_t i = 0; i < statesToEnter.size(); i++) { - std::cout << ATTR_CAST(statesToEnter[i], "id") << ", "; - } - std::cout << std::endl; -#endif - - for (size_t i = 0; i < statesToEnter.size(); i++) { - Element<std::string> stateElem = (Element<std::string>)statesToEnter[i]; - - // extension for flattened interpreters - for (unsigned int k = 0; k < statesToEnter.size(); k++) { - NodeSet<std::string> invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", statesToEnter[k]); - for (unsigned int j = 0; j < invokes.size(); j++) { - Element<std::string> invokeElem = Element<std::string>(invokes[j]); - if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { - invoke(invokeElem); - } - } - } - - USCXML_MONITOR_CALLBACK3(beforeEnteringState, stateElem, (i + 1 < statesToEnter.size())) - - // extension for flattened SCXML documents, we will need an explicit uninvoke element - NodeSet<std::string> uninvokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "uninvoke", statesToEnter[i]); - for (size_t j = 0; j < uninvokes.size(); j++) { - Element<std::string> uninvokeElem = (Element<std::string>)uninvokes[j]; - cancelInvoke(uninvokeElem); - } - - _configuration.push_back(stateElem); - _statesToInvoke.push_back(stateElem); - - if (_binding == LATE && !isMember(stateElem, _alreadyEntered)) { - NodeSet<std::string> dataModelElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", stateElem); - if(dataModelElems.size() > 0 && _dataModel) { - Arabica::XPath::NodeSet<std::string> dataElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "data", dataModelElems[0]); - for (size_t j = 0; j < dataElems.size(); j++) { - if (dataElems[j].getNodeType() == Node_base::ELEMENT_NODE) - initializeData(Element<std::string>(dataElems[j])); - } - } - _alreadyEntered.push_back(stateElem); - } - // execute onentry executable content - NodeSet<std::string> onEntryElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "onEntry", stateElem); - executeContent(onEntryElems, false); - - USCXML_MONITOR_CALLBACK3(afterEnteringState, stateElem, (i + 1 < statesToEnter.size())) - - if (isMember(stateElem, statesForDefaultEntry)) { - // execute initial transition content for compound states - Arabica::XPath::NodeSet<std::string> transitions = _xpath.evaluate("" + _nsInfo.xpathPrefix + "initial/" + _nsInfo.xpathPrefix + "transition", stateElem).asNodeSet(); - for (size_t j = 0; j < transitions.size(); j++) { - executeContent(Element<std::string>(transitions[j])); - } - } - -#if 0 - // not working yet - if (isMember(stateElem, defaultHistoryContent)) { - // execute history transition - Arabica::XPath::NodeSet<std::string> transitions = _xpath.evaluate("" + _nsInfo.xpathPrefix + "history/" + _nsInfo.xpathPrefix + "transition", stateElem).asNodeSet(); - for (size_t j = 0; j < transitions.size(); j++) { - executeContent(transitions[j]); - } - } -#endif - if (isFinal(stateElem)) { - - Arabica::DOM::Element<std::string> doneData; - Arabica::XPath::NodeSet<std::string> doneDatas = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "donedata", stateElem); - if (doneDatas.size() > 0) { - // only process first donedata element - doneData = Element<std::string>(doneDatas[0]); - } - - internalDoneSend(stateElem, doneData); - Node<std::string> parent = stateElem.getParentNode(); - - if (parent.getNodeType() == Node_base::ELEMENT_NODE && - parent.getParentNode().getNodeType() == Node_base::ELEMENT_NODE && - isParallel(Element<std::string>(parent.getParentNode()))) { - Element<std::string> grandParent = (Element<std::string>)parent.getParentNode(); - - Arabica::XPath::NodeSet<std::string> childs = getChildStates(grandParent); - bool inFinalState = true; - for (size_t j = 0; j < childs.size(); j++) { - if (!isInFinalState(Element<std::string>(childs[j]))) { - inFinalState = false; - break; - } - } - if (inFinalState) { - internalDoneSend(Element<std::string>(parent), Arabica::DOM::Element<std::string>()); - } - } - } - } - for (size_t i = 0; i < _configuration.size(); i++) { - Element<std::string> stateElem = (Element<std::string>)_configuration[i]; - if (isFinal(stateElem) && parentIsScxmlState(stateElem)) { - _topLevelFinalReached = true; - } - } -} - -void InterpreterDraft6::addStatesToEnter(const Element<std::string>& state, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - Arabica::XPath::NodeSet<std::string>& defaultHistoryContent) { - std::string stateId = ((Element<std::string>)state).getAttribute("id"); - -#if VERBOSE - std::cout << "Adding state to enter: " << stateId << std::endl; -#endif - if (isHistory(state)) { - if (_historyValue.find(stateId) != _historyValue.end()) { - Arabica::XPath::NodeSet<std::string> historyValue = _historyValue[stateId]; - -#if VERBOSE - std::cout << "History State " << ATTR(state, "id") << ": "; - for (size_t i = 0; i < historyValue.size(); i++) { - std::cout << ATTR_CAST(historyValue[i], "id") << ", "; - } - std::cout << std::endl; -#endif - - for (size_t i = 0; i < historyValue.size(); i++) { - addStatesToEnter(Element<std::string>(historyValue[i]), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - NodeSet<std::string> ancestors = getProperAncestors(historyValue[i], state); - -#if VERBOSE - std::cout << "Proper Ancestors: "; - for (size_t j = 0; j < ancestors.size(); j++) { - std::cout << ATTR_CAST(ancestors[j], "id") << ", "; - } - std::cout << std::endl; -#endif - - for (size_t j = 0; j < ancestors.size(); j++) { - statesToEnter.push_back(ancestors[j]); - } - } - } else { - defaultHistoryContent.push_back(getParentState(state)); - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", state); - for (size_t i = 0; i < transitions.size(); i++) { - NodeSet<std::string> targets = getTargetStates(Element<std::string>(transitions[i])); - for (size_t j = 0; j < targets.size(); j++) { - addStatesToEnter(Element<std::string>(targets[j]), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - - // Modifications from chris nuernberger - NodeSet<std::string> ancestors = getProperAncestors(targets[j], state); - for (size_t k = 0; k < ancestors.size(); k++) { - statesToEnter.push_back(ancestors[k]); - } - } - } - } - } else { - statesToEnter.push_back(state); - if (isCompound(state)) { - statesForDefaultEntry.push_back(state); - - NodeSet<std::string> tStates = getInitialStates(state); - for (size_t i = 0; i < tStates.size(); i++) { - addStatesToEnter(Element<std::string>(tStates[i]), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - - // addStatesToEnter(getInitialState(state), statesToEnter, statesForDefaultEntry); - // NodeSet<std::string> tStates = getTargetStates(getInitialState(state)); - - } else if(isParallel(state)) { - NodeSet<std::string> childStates = getChildStates(state); - for (size_t i = 0; i < childStates.size(); i++) { - addStatesToEnter(Element<std::string>(childStates[i]), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - } - } -} - -void InterpreterDraft6::handleDOMEvent(Arabica::DOM::Events::Event<std::string>& event) { - InterpreterImpl::handleDOMEvent(event); - - // remove modified states from cache - if (event.getType().compare("DOMAttrModified") == 0) // we do not care about attributes - return; - Node<std::string> target = Arabica::DOM::Node<std::string>(event.getTarget()); - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", target, true); - for (size_t i = 0; i < transitions.size(); i++) { - const Element<std::string> transElem = Element<std::string>(transitions[i]); - if (_transWithinParallel.find(transElem) != _transWithinParallel.end()) - _transWithinParallel.erase(transElem); - } -} - - -}
\ No newline at end of file diff --git a/src/uscxml/interpreter/InterpreterDraft6.h b/src/uscxml/interpreter/InterpreterDraft6.h deleted file mode 100644 index 6a1275b..0000000 --- a/src/uscxml/interpreter/InterpreterDraft6.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @file - * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see <http://www.opensource.org/licenses/bsd-license>. - * @endcond - */ - -#ifndef INTERPRETERDRAFT6_H_JAXK9FE1 -#define INTERPRETERDRAFT6_H_JAXK9FE1 - -#include "uscxml/Interpreter.h" - -namespace uscxml { - -class USCXML_API InterpreterDraft6 : public InterpreterImpl { -public: - virtual ~InterpreterDraft6() {}; - -protected: - - void enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); - void addStatesToEnter(const Arabica::DOM::Element<std::string>& state, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - Arabica::XPath::NodeSet<std::string>& defaultHistoryContent); - - void exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); - - Arabica::XPath::NodeSet<std::string> removeConflictingTransitions(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); - bool isPreemptingTransition(const Arabica::DOM::Element<std::string>& t1, const Arabica::DOM::Element<std::string>& t2); - - bool isCrossingBounds(const Arabica::DOM::Element<std::string>& transition); - bool isWithinParallel(const Arabica::DOM::Element<std::string>& transition); - Arabica::DOM::Node<std::string> findLCPA(const Arabica::XPath::NodeSet<std::string>& states); - - std::map<Arabica::DOM::Element<std::string>, bool> _transWithinParallel; // this is costly to calculate - - virtual void handleDOMEvent(Arabica::DOM::Events::Event<std::string>& event); - -}; - -} - -#endif /* end of include guard: INTERPRETERDRAFT6_H_JAXK9FE1 */ diff --git a/src/uscxml/interpreter/InterpreterFast.cpp b/src/uscxml/interpreter/InterpreterFast.cpp deleted file mode 100644 index ab5dce0..0000000 --- a/src/uscxml/interpreter/InterpreterFast.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file - * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see <http://www.opensource.org/licenses/bsd-license>. - * @endcond - */ - -#include "InterpreterFast.h" - -#include "uscxml/Factory.h" -#include "uscxml/concurrency/DelayedEventQueue.h" - -#include <glog/logging.h> -#include "uscxml/UUID.h" -#include "uscxml/dom/DOMUtils.h" - -namespace uscxml { - -using namespace Arabica::XPath; -using namespace Arabica::DOM; - - -void InterpreterFast::handleDOMEvent(Arabica::DOM::Events::Event<std::string>& event) { - InterpreterImpl::handleDOMEvent(event); - - if (event.getType().compare("DOMAttrModified") == 0) // we do not care about attributes - return; - -} -}
\ No newline at end of file diff --git a/src/uscxml/interpreter/InterpreterFast.h b/src/uscxml/interpreter/InterpreterFast.h deleted file mode 100644 index 5838dc0..0000000 --- a/src/uscxml/interpreter/InterpreterFast.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file - * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see <http://www.opensource.org/licenses/bsd-license>. - * @endcond - */ - -#ifndef INTERPRETERFAST_H_224A5F07 -#define INTERPRETERFAST_H_224A5F07 - -#include "uscxml/Interpreter.h" - -namespace uscxml { - -class InterpreterFast : public InterpreterImpl { -protected: - virtual void setupSets(); - virtual void handleDOMEvent(Arabica::DOM::Events::Event<std::string>& event); - -private: - - /* TODO: use post-order and document-order per STL comparator (sorted std::set?) */ - - std::vector<Arabica::XPath::NodeSet<std::string> > _states; - std::vector<Arabica::XPath::NodeSet<std::string> > _transitions; - - std::vector<std::vector<bool> > _conflictingTransitions; - std::vector<std::vector<bool> > _exitSets; - std::vector<std::vector<bool> > _targetSets; - -}; - -} - -#endif /* end of include guard: INTERPRETERFAST_H_224A5F07 */ diff --git a/src/uscxml/interpreter/InterpreterImpl.cpp b/src/uscxml/interpreter/InterpreterImpl.cpp new file mode 100644 index 0000000..ba75ab8 --- /dev/null +++ b/src/uscxml/interpreter/InterpreterImpl.cpp @@ -0,0 +1,361 @@ +/** + * @file + * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#include "uscxml/Common.h" +#include "uscxml/util/UUID.h" +#include "uscxml/Interpreter.h" +#include "uscxml/messages/Event.h" +#include "uscxml/util/String.h" +#include "uscxml/util/Predicates.h" + +#include "easylogging++.h" + +#include <iostream> + +#include <assert.h> +#include <algorithm> +#include <memory> +#include <mutex> + +#include "uscxml/interpreter/MicroStepFast.h" + +#define VERBOSE 0 + +namespace uscxml { + +using namespace xercesc; + +std::map<std::string, std::weak_ptr<InterpreterImpl> > InterpreterImpl::_instances; +std::recursive_mutex InterpreterImpl::_instanceMutex; + +std::map<std::string, std::weak_ptr<InterpreterImpl> > InterpreterImpl::getInstances() { + std::lock_guard<std::recursive_mutex> lock(_instanceMutex); + std::map<std::string, std::weak_ptr<InterpreterImpl> >::iterator instIter = _instances.begin(); + while(instIter != _instances.end()) { + if (!instIter->second.lock()) { + _instances.erase(instIter++); + } else { + instIter++; + } + } + return _instances; +} + +void InterpreterImpl::addInstance(std::shared_ptr<InterpreterImpl> interpreterImpl) { + std::lock_guard<std::recursive_mutex> lock(_instanceMutex); + assert(_instances.find(interpreterImpl->getSessionId()) == _instances.end()); + _instances[interpreterImpl->getSessionId()] = interpreterImpl; +} + +InterpreterImpl::InterpreterImpl() : _isInitialized(false), _document(NULL), _scxml(NULL), _state(USCXML_INSTANTIATED), _monitor(NULL) { + try { + xercesc::XMLPlatformUtils::Initialize(); + } catch (const xercesc::XMLException& toCatch) { + ERROR_PLATFORM_THROW("Cannot initialize XercesC: " + X(toCatch.getMessage()).str()); + } + + _sessionId = UUID::getUUID(); + _factory = Factory::getInstance(); +} + + +InterpreterImpl::~InterpreterImpl() { + if (_delayQueue) + _delayQueue.cancelAllDelayed(); + if (_document) + delete _document; + { + std::lock_guard<std::recursive_mutex> lock(_instanceMutex); + _instances.erase(getSessionId()); + } +} + +void InterpreterImpl::cancel() { + _microStepper.markAsCancelled(); + Event unblock; + enqueueExternal(unblock); +} + + +void InterpreterImpl::setupDOM() { + + if (!_document) { + ERROR_PLATFORM_THROW("Interpreter has no XML document"); + } + + if (!_scxml) { + // find scxml element + DOMNodeList* scxmls = NULL; + + // proper namespace + scxmls = _document->getElementsByTagNameNS(X("http://www.w3.org/2005/07/scxml"), X("scxml")); + if (scxmls->getLength() > 0) + goto SCXML_STOP_SEARCH; + + // no namespace + scxmls = _document->getElementsByTagName(X("scxml")); + if (scxmls->getLength() > 0) + goto SCXML_STOP_SEARCH; + +SCXML_STOP_SEARCH: + if (scxmls->getLength() == 0) { + ERROR_PLATFORM_THROW("Cannot find SCXML element in DOM"); + return; + } + + _scxml = dynamic_cast<DOMElement*>(scxmls->item(0)); + + _xmlPrefix = _scxml->getPrefix(); + _xmlNS = _scxml->getNamespaceURI(); + if (_xmlPrefix) { + _xmlPrefix = std::string(_xmlPrefix) + ":"; + } + if (HAS_ATTR(_scxml, "name")) { + _name = ATTR(_scxml, "name"); + } else { + _name = _baseURL.pathComponents().back(); + } + + _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); + + } + +} + +void InterpreterImpl::init() { + + if (_isInitialized) + return; + + setupDOM(); + + // register io processors + std::map<std::string, IOProcessorImpl*> ioProcs = _factory->getIOProcessors(); + for (auto ioProcIter = ioProcs.begin(); ioProcIter != ioProcs.end(); ioProcIter++) { + + // do not override if already set + if (_ioProcs.find(ioProcIter->first) != _ioProcs.end()) { + ioProcIter++; + continue; + } + + // this might throw error.execution + _ioProcs[ioProcIter->first] = _factory->createIOProcessor(ioProcIter->first, this); + + // register aliases + std::list<std::string> names = _ioProcs[ioProcIter->first].getNames(); + for (auto nameIter = names.begin(); nameIter != names.end(); nameIter++) { + // do not override + if (!iequals(*nameIter, ioProcIter->first) && _ioProcs.find(*nameIter) == _ioProcs.end()) { + _ioProcs[*nameIter] = _ioProcs[ioProcIter->first]; + } + } + + } + + if (!_microStepper) { + _microStepper = MicroStep(std::shared_ptr<MicroStepImpl>(new MicroStepFast(this))); + } + _microStepper.init(_scxml); + + if (!_dataModel) { + _dataModel = _factory->createDataModel(HAS_ATTR(_scxml, "datamodel") ? ATTR(_scxml, "datamodel") : "null", this); + } + if (!_execContent) { + _execContent = ContentExecutor(std::shared_ptr<ContentExecutorImpl>(new BasicContentExecutorImpl(this))); + } + + if (!_externalQueue) { + _externalQueue = EventQueue(std::shared_ptr<EventQueueImpl>(new EventQueueImpl())); + } + if (!_internalQueue) { + _internalQueue = EventQueue(std::shared_ptr<EventQueueImpl>(new EventQueueImpl())); + } + if (!_delayQueue) { + _delayQueue = DelayedEventQueue(std::shared_ptr<DelayedEventQueueImpl>(new DelayedEventQueueImpl(this))); + } + + _isInitialized = true; +} + +void InterpreterImpl::initData(xercesc::DOMElement* root) { + std::string id = ATTR(root, "id"); + Data d; + try { + if (Event::getParam(_invokeReq.params, id, d)) { + _dataModel.init(id, d); + } else if (_invokeReq.namelist.find(id) != _invokeReq.namelist.end()) { + _dataModel.init(id, _invokeReq.namelist[id]); + } else { + _dataModel.init(id, _execContent.elementAsData(root)); + } + } catch(ErrorEvent e) { + // test 277 + enqueueInternal(e); + } +} + +void InterpreterImpl::assign(const std::string& location, const Data& data) { + _dataModel.assign(location, data); +} + +bool InterpreterImpl::isMatched(const Event& event, const std::string& eventDesc) { + return nameMatch(eventDesc, event.name); +} + +bool InterpreterImpl::isTrue(const std::string& expr) { + try { + return _dataModel.evalAsBool(expr); + } catch (ErrorEvent e) { + // test 244: deliver error execution + + LOG(ERROR) << e; + + // test 344 + enqueueInternal(e); + // test 245: undefined is false + return false; + + } +} + + +bool InterpreterImpl::checkValidSendType(const std::string& type, const std::string& target) { + + // this is the responsibility of the calling method + assert(type.size() > 0); + + if (_ioProcs.find(type) == _ioProcs.end()) { + ERROR_EXECUTION_THROW("Type '" + type + "' not supported for sending"); + } + + if (!_ioProcs[type].isValidTarget(target)) { + ERROR_COMMUNICATION_THROW("Target '" + target + "' not supported in send"); + } + + return true; +} + +Event InterpreterImpl::dequeueExternal(bool blocking) { + _currEvent = _externalQueue.dequeue(blocking); + if (_currEvent) { + _dataModel.setEvent(_currEvent); + +// LOG(ERROR) << e.name; + + // test 233 + if (_currEvent.invokeid.size() > 0 && + _invokers.find(_currEvent.invokeid) != _invokers.end() && + _invokers[_currEvent.invokeid].getFinalize() != NULL) { + _execContent.process(_invokers[_currEvent.invokeid].getFinalize(), _xmlPrefix); + } + + for (auto invIter = _invokers.begin(); invIter != _invokers.end(); invIter++) { + // test 229 + if (_autoForwarders.find(invIter->first) != _autoForwarders.end()) { + invIter->second.eventFromSCXML(_currEvent); + } + } + } + return _currEvent; +} + +void InterpreterImpl::enqueue(const std::string& type, const std::string& target, size_t delayMs, const Event& sendEvent) { + std::lock_guard<std::recursive_mutex> lock(_delayMutex); + + assert(sendEvent.uuid.length() > 0); + assert(_delayedEventTargets.find(sendEvent.uuid) == _delayedEventTargets.end()); + + _delayedEventTargets[sendEvent.uuid] = std::tuple<std::string, std::string, std::string>(sendEvent.sendid, type, target); + if (delayMs == 0) { + Event copy(sendEvent); + return eventReady(copy, sendEvent.uuid); + } else { + return _delayQueue.enqueueDelayed(sendEvent, delayMs, sendEvent.uuid); + } +} + +void InterpreterImpl::cancelDelayed(const std::string& sendId) { + std::lock_guard<std::recursive_mutex> lock(_delayMutex); + + // we need to find the uuids for the given sendid + for (auto evIter = _delayedEventTargets.begin(); evIter != _delayedEventTargets.end();) { + // inline deletion for maps: http://stackoverflow.com/a/263958/990120 + if (std::get<0>(evIter->second) == sendId) { + _delayQueue.cancelDelayed(evIter->first); + evIter = _delayedEventTargets.erase(evIter); + } else { + evIter++; + } + } +} + +void InterpreterImpl::eventReady(Event& sendEvent, const std::string& eventUUID) { + std::lock_guard<std::recursive_mutex> lock(_delayMutex); + + // we only arrive here after the delay already passed! + assert(_delayedEventTargets.find(eventUUID) != _delayedEventTargets.end()); + + std::string type = std::get<1>(_delayedEventTargets[eventUUID]); + std::string target = std::get<2>(_delayedEventTargets[eventUUID]); + + // test 172 + if (type.size() == 0) { + type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; + } + + _delayedEventTargets.erase(eventUUID); + + if (_ioProcs.find(type) != _ioProcs.end()) { + _ioProcs[type].eventFromSCXML(target, sendEvent); + } else { + ERROR_PLATFORM_THROW("No IO processor " + type + " known"); + } +} + +void InterpreterImpl::invoke(const std::string& type, const std::string& src, bool autoForward, xercesc::DOMElement* finalize, const Event& invokeEvent) { + + std::string tmp; + if (src.size() > 0) { + URL url(src); + if (!url.isAbsolute()) { + url = URL::resolve(url, _baseURL); + } + tmp = (std::string)url; + } + + std::shared_ptr<InvokerImpl> invokerImpl = _factory->createInvoker(type, this); + invokerImpl->setFinalize(finalize); + _invokers[invokeEvent.invokeid] = invokerImpl; + _invokers[invokeEvent.invokeid].invoke(tmp, invokeEvent); + + if (autoForward) { + _autoForwarders.insert(invokeEvent.invokeid); + } +} + +void InterpreterImpl::uninvoke(const std::string& invokeId) { + if (_invokers.find(invokeId) != _invokers.end()) { + _invokers[invokeId].uninvoke(); + _autoForwarders.erase(invokeId); + } + +} + +} diff --git a/src/uscxml/interpreter/InterpreterImpl.h b/src/uscxml/interpreter/InterpreterImpl.h new file mode 100644 index 0000000..7c64779 --- /dev/null +++ b/src/uscxml/interpreter/InterpreterImpl.h @@ -0,0 +1,290 @@ +/** + * @file + * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#ifndef INTERPRETERIMPL_H_29D5BEBA +#define INTERPRETERIMPL_H_29D5BEBA + +#include <memory> +#include <mutex> +#include <list> +#include <map> +#include <string> + +#include "uscxml/Common.h" +#include "uscxml/util/URL.h" +#include "uscxml/plugins/Factory.h" +#include "uscxml/plugins/DataModel.h" +#include "uscxml/interpreter/MicroStepImpl.h" +#include "uscxml/interpreter/ContentExecutorImpl.h" +#include "uscxml/interpreter/EventQueueImpl.h" +#include "uscxml/util/DOM.h" +#include <xercesc/dom/DOM.hpp> + +namespace uscxml { + +class InterpreterMonitor; +class InterpreterIssue; + +class USCXML_API ActionLanguage { +public: + MicroStep microStepper; + DataModel dataModel; + ContentExecutor execContent; +}; + +class USCXML_API InterpreterImpl : + public MicroStepCallbacks, + public DataModelCallbacks, + public ContentExecutorCallbacks, + public DelayedEventQueueCallbacks +// public std::enable_shared_from_this<InterpreterImpl> +{ +public: + enum Binding { + EARLY = 0, + LATE = 1 + }; + + InterpreterImpl(); + virtual ~InterpreterImpl(); + + void cloneFrom(InterpreterImpl* other); + void cloneFrom(std::shared_ptr<InterpreterImpl> other); + + virtual InterpreterState step(bool blocking) { + if (!_isInitialized) { + init(); + _state = USCXML_INITIALIZED; + } else { + _state = _microStepper.step(blocking); + } + return _state; + } + + virtual void reset() {///< Reset state machine + _microStepper.reset(); + _isInitialized = false; + _state = USCXML_INSTANTIATED; +// _dataModel.reset(); +// _eventQueue.reset(); +// _contentExecutor.reset(); + } + + virtual void cancel(); ///< Cancel and finalize state machine + + InterpreterState getState() { + return _state; + } + + std::list<xercesc::DOMElement*> getConfiguration() { + return _microStepper.getConfiguration(); + } + + void setMonitor(InterpreterMonitor* monitor) { + _monitor = monitor; + } + + /** + MicrostepCallbacks + */ + virtual Event dequeueInternal() { + _currEvent = _internalQueue.dequeue(false); + if (_currEvent) + _dataModel.setEvent(_currEvent); + return _currEvent; + } + virtual Event dequeueExternal(bool blocking); + virtual bool isTrue(const std::string& expr); + + virtual void raiseDoneEvent(xercesc::DOMElement* state, xercesc::DOMElement* doneData) { + _execContent.raiseDoneEvent(state, doneData); + } + + virtual void process(xercesc::DOMElement* block) { + _execContent.process(block, _xmlPrefix); + } + + virtual bool isMatched(const Event& event, const std::string& eventDesc); + virtual void initData(xercesc::DOMElement* element); + + virtual void invoke(xercesc::DOMElement* invoke) { + _execContent.invoke(invoke); + } + + virtual void uninvoke(xercesc::DOMElement* invoke) { + _execContent.uninvoke(invoke); + } + + virtual InterpreterMonitor* getMonitor() { + return _monitor; + } + + /** + DataModelCallbacks + */ + virtual const std::string& getName() { + return _name; + } + virtual const std::string& getSessionId() { + return _sessionId; + } + virtual const std::map<std::string, IOProcessor>& getIOProcessors() { + return _ioProcs; + } + virtual const std::map<std::string, Invoker>& getInvokers() { + return _invokers; + } + + virtual bool isInState(const std::string& stateId) { + return _microStepper.isInState(stateId); + } + virtual xercesc::DOMDocument* getDocument() const { + return _document; + } + + /** + ContentExecutorCallbacks + */ + + virtual void enqueueInternal(const Event& event) { + return _internalQueue.enqueue(event); + } + virtual void enqueueExternal(const Event& event) { + return _externalQueue.enqueue(event); + } + virtual void enqueueExternalDelayed(const Event& event, size_t delayMs, const std::string& eventUUID) { + return _delayQueue.enqueueDelayed(event, delayMs, eventUUID); + } + virtual void cancelDelayed(const std::string& eventId); + + virtual size_t getLength(const std::string& expr) { + return _dataModel.getLength(expr); + } + + virtual void setForeach(const std::string& item, + const std::string& array, + const std::string& index, + uint32_t iteration) { + return _dataModel.setForeach(item, array, index, iteration); + } + virtual Data evalAsData(const std::string& expr) { + return _dataModel.evalAsData(expr); + } + + virtual Data getAsData(const std::string& expr) { + return _dataModel.getAsData(expr); + } + + virtual void assign(const std::string& location, const Data& data); + + virtual std::string getInvokeId() { + return _invokeId; + } + virtual std::string getBaseURL() { + return _baseURL; + } + + virtual bool checkValidSendType(const std::string& type, const std::string& target); + virtual void invoke(const std::string& type, const std::string& src, bool autoForward, xercesc::DOMElement* finalize, const Event& invokeEvent); + virtual void uninvoke(const std::string& invokeId); + virtual void enqueue(const std::string& type, const std::string& target, size_t delayMs, const Event& sendEvent); + + virtual const Event& getCurrentEvent() { + return _currEvent; + } + + /** + DelayedEventQueueCallbacks + */ + + virtual void eventReady(Event& event, const std::string& eventUUID); + + /** --- */ + + void setActionLanguage(const ActionLanguage& al) { + _execContent = al.execContent; + _microStepper = al.microStepper; + _dataModel = al.dataModel; + } + + static std::map<std::string, std::weak_ptr<InterpreterImpl> > getInstances(); + + virtual xercesc::DOMDocument* getDocument() { + return _document; + } + +protected: + static void addInstance(std::shared_ptr<InterpreterImpl> instance); + + Binding _binding; + + std::string _sessionId; + std::string _name; + std::string _invokeId; // TODO: Never set! + + bool _isInitialized; + xercesc::DOMDocument* _document; + xercesc::DOMElement* _scxml; + + std::map<std::string, std::tuple<std::string, std::string, std::string> > _delayedEventTargets; + + virtual void init(); + + static std::map<std::string, std::weak_ptr<InterpreterImpl> > _instances; + static std::recursive_mutex _instanceMutex; + std::recursive_mutex _delayMutex; + + friend class Interpreter; + friend class InterpreterIssue; + friend class TransformerImpl; + friend class USCXMLInvoker; + friend class SCXMLIOProcessor; + + X _xmlPrefix; + X _xmlNS; + Factory* _factory; + + URL _baseURL; + + MicroStep _microStepper; + DataModel _dataModel; + ContentExecutor _execContent; + + InterpreterState _state; + + EventQueue _internalQueue; + EventQueue _externalQueue; + EventQueue _parentQueue; + DelayedEventQueue _delayQueue; + + Event _currEvent; + Event _invokeReq; + + std::map<std::string, IOProcessor> _ioProcs; + std::map<std::string, Invoker> _invokers; + std::set<std::string> _autoForwarders; + InterpreterMonitor* _monitor; + +private: + void setupDOM(); +}; + +} + +#endif /* end of include guard: INTERPRETERIMPL_H_29D5BEBA */ diff --git a/src/uscxml/interpreter/InterpreterMonitor.h b/src/uscxml/interpreter/InterpreterMonitor.h new file mode 100644 index 0000000..6ebdb35 --- /dev/null +++ b/src/uscxml/interpreter/InterpreterMonitor.h @@ -0,0 +1,95 @@ +/** + * @file + * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#ifndef INTERPRETERMONITOR_H_4BA77097 +#define INTERPRETERMONITOR_H_4BA77097 + +#include "uscxml/Common.h" +#include "uscxml/messages/Event.h" +#include "uscxml/debug/InterpreterIssue.h" + +#include <mutex> + +#define USCXML_MONITOR_CATCH(callback) \ +catch (Event e) { LOG(ERROR) << "Syntax error when calling " #callback " on monitors: " << std::endl << e << std::endl; } \ +catch (std::bad_weak_ptr e) { LOG(ERROR) << "Unclean shutdown " << std::endl; } \ +catch (...) { LOG(ERROR) << "An exception occurred when calling " #callback " on monitors"; } \ +if (_state == USCXML_DESTROYED) { throw std::bad_weak_ptr(); } + +#define USCXML_MONITOR_CALLBACK(callback, function) \ +if (callback) { callback->function(); } + +#define USCXML_MONITOR_CALLBACK1(callback, function, arg1) \ +if (callback) { callback->function(arg1); } + +#define USCXML_MONITOR_CALLBACK2(callback, function, arg1, arg2) \ +if (callback) { callback->function(arg1, arg2); } + +namespace uscxml { + +class USCXML_API InterpreterMonitor { +public: + InterpreterMonitor() : _copyToInvokers(false) {} + virtual ~InterpreterMonitor() {} + + virtual void beforeProcessingEvent(const Event& event) {} + virtual void beforeMicroStep() {} + + virtual void beforeExitingState(const xercesc::DOMElement* state) {} + virtual void afterExitingState(const xercesc::DOMElement* state) {} + + virtual void beforeExecutingContent(const xercesc::DOMElement* execContent) {} + virtual void afterExecutingContent(const xercesc::DOMElement* execContent) {} + + virtual void beforeUninvoking(const xercesc::DOMElement* invokeElem, const std::string& invokeid) {} + virtual void afterUninvoking(const xercesc::DOMElement* invokeElem, const std::string& invokeid) {} + + virtual void beforeTakingTransition(const xercesc::DOMElement* transition) {} + virtual void afterTakingTransition(const xercesc::DOMElement* transition) {} + + virtual void beforeEnteringState(const xercesc::DOMElement* state) {} + virtual void afterEnteringState(const xercesc::DOMElement* state) {} + + virtual void beforeInvoking(const xercesc::DOMElement* invokeElem, const std::string& invokeid) {} + virtual void afterInvoking(const xercesc::DOMElement* invokeElem, const std::string& invokeid) {} + + virtual void afterMicroStep() {} + virtual void onStableConfiguration() {} + + virtual void beforeCompletion() {} + virtual void afterCompletion() {} + + virtual void reportIssue(const InterpreterIssue& issue) {} + + void copyToInvokers(bool copy) { + _copyToInvokers = copy; + } + + bool copyToInvokers() { + return _copyToInvokers; + } + +protected: + bool _copyToInvokers; + +}; + +} + +#endif /* end of include guard: INTERPRETERMONITOR_H_4BA77097 */ diff --git a/src/uscxml/interpreter/InterpreterRC.cpp b/src/uscxml/interpreter/InterpreterRC.cpp deleted file mode 100644 index b594c18..0000000 --- a/src/uscxml/interpreter/InterpreterRC.cpp +++ /dev/null @@ -1,661 +0,0 @@ -/** - * @file - * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see <http://www.opensource.org/licenses/bsd-license>. - * @endcond - */ - -#include "InterpreterRC.h" - -#include "uscxml/Factory.h" -#include "uscxml/concurrency/DelayedEventQueue.h" - -#include <glog/logging.h> -#include "uscxml/UUID.h" -#include "uscxml/dom/DOMUtils.h" - -#define VERBOSE 0 -#define VERBOSE_STATE_SELECTION 0 -#define VERBOSE_FIND_LCCA 0 - -namespace uscxml { - -using namespace Arabica::XPath; -using namespace Arabica::DOM; - -#if 1 -size_t padding = 0; -std::string getPadding() { - std::string pad = ""; - for (size_t i = 0; i < padding; i++) { - pad += " "; - } - return pad; -} -#endif - -Arabica::XPath::NodeSet<std::string> InterpreterRC::removeConflictingTransitions(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { - Arabica::XPath::NodeSet<std::string> filteredTransitions; - for (unsigned int i = 0; i < enabledTransitions.size(); i++) { - Element<std::string> t1(enabledTransitions[i]); - bool t1Preempted = false; - Arabica::XPath::NodeSet<std::string> transitionsToRemove; - - for (unsigned int j = 0; j < filteredTransitions.size(); j++) { - Element<std::string> t2(filteredTransitions[j]); - if (hasIntersection(computeExitSet(t1), computeExitSet(t2))) { - if (isDescendant(getSourceState(t1), getSourceState(t2))) { - transitionsToRemove.push_back(t2); - } else { - t1Preempted = true; - break; - } - } - } - - if (!t1Preempted) { - // remove transitionsToRemove from DOMUtils::filteredTransitions - std::list<Node<std::string> > tmp; - for (size_t i = 0; i < filteredTransitions.size(); i++) { - if (!isMember(filteredTransitions[i], transitionsToRemove)) { - tmp.push_back(filteredTransitions[i]); - } - } - filteredTransitions = NodeSet<std::string>(); - filteredTransitions.insert(filteredTransitions.end(), tmp.begin(), tmp.end()); - - filteredTransitions.push_back(t1); - } - } - return filteredTransitions; -} - -bool InterpreterRC::hasIntersection(const Arabica::XPath::NodeSet<std::string>& nodeSet1, const Arabica::XPath::NodeSet<std::string>& nodeSet2) { - for (unsigned int i = 0; i < nodeSet1.size(); i++) { - for (unsigned int j = 0; j < nodeSet2.size(); j++) { - if (nodeSet1[i] == nodeSet2[j]) - return true; - } - } - return false; -} - - -void InterpreterRC::exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { - NodeSet<std::string> statesToExit = computeExitSet(enabledTransitions); - -#if VERBOSE_STATE_SELECTION - std::cout << "exitStates: "; - for (size_t i = 0; i < statesToExit.size(); i++) { - std::cout << ATTR_CAST(statesToExit[i], "id") << " "; - } - std::cout << std::endl; -#endif - - // remove statesToExit from _statesToInvoke - std::list<Node<std::string> > tmp; - for (size_t i = 0; i < _statesToInvoke.size(); i++) { - if (!isMember(_statesToInvoke[i], statesToExit)) { - tmp.push_back(_statesToInvoke[i]); - } - } - _statesToInvoke = NodeSet<std::string>(); - _statesToInvoke.insert(_statesToInvoke.end(), tmp.begin(), tmp.end()); - - statesToExit.forward(false); - statesToExit.sort(); - - - for (size_t i = 0; i < statesToExit.size(); i++) { - NodeSet<std::string> histories = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "history", statesToExit[i]); - for (size_t j = 0; j < histories.size(); j++) { - Element<std::string> historyElem = (Element<std::string>)histories[j]; - std::string historyType = (historyElem.hasAttribute("type") ? historyElem.getAttribute("type") : "shallow"); - NodeSet<std::string> historyNodes; - for (size_t k = 0; k < _configuration.size(); k++) { - if (iequals(historyType, "deep")) { - if (isAtomic(Element<std::string>(_configuration[k])) && isDescendant(_configuration[k], statesToExit[i])) - historyNodes.push_back(_configuration[k]); - } else { - if (_configuration[k].getParentNode() == statesToExit[i]) - historyNodes.push_back(_configuration[k]); - } - } - _historyValue[historyElem.getAttribute("id")] = historyNodes; - } - } - - for (size_t i = 0; i < statesToExit.size(); i++) { - USCXML_MONITOR_CALLBACK3(beforeExitingState, Element<std::string>(statesToExit[i]), (i + 1 < statesToExit.size())) - - NodeSet<std::string> onExits = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "onExit", statesToExit[i]); - for (size_t j = 0; j < onExits.size(); j++) { - Element<std::string> onExitElem = (Element<std::string>)onExits[j]; - executeContent(onExitElem); - } - - USCXML_MONITOR_CALLBACK3(afterExitingState, Element<std::string>(statesToExit[i]), (i + 1 < statesToExit.size())) - - NodeSet<std::string> invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", statesToExit[i]); - for (size_t j = 0; j < invokes.size(); j++) { - Element<std::string> invokeElem = (Element<std::string>)invokes[j]; - if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { - } else { - cancelInvoke(invokeElem); - } - } - - // remove statesToExit[i] from _configuration - test409 - tmp.clear(); - for (size_t j = 0; j < _configuration.size(); j++) { - if (_configuration[j] != statesToExit[i]) { - tmp.push_back(_configuration[j]); - } - } - _configuration = NodeSet<std::string>(); - _configuration.insert(_configuration.end(), tmp.begin(), tmp.end()); - } -} - -/* -function computeExitSet(transitions) - statesToExit = new OrderedSet - for t in transitions: - if(t.target): - domain = getTransitionDomain(t) - for s in configuration: - if isDescendant(s,domain): - statesToExit.add(s) - return statesToExit -*/ -Arabica::XPath::NodeSet<std::string> InterpreterRC::computeExitSet(const Arabica::XPath::NodeSet<std::string>& transitions) { - - NodeSet<std::string> statesToExit; - for (unsigned int i = 0; i < transitions.size(); i++) { - Element<std::string> t(transitions[i]); - if (!isTargetless(t)) { - Arabica::DOM::Node<std::string> domain = getTransitionDomain(t); - if (!domain) - continue; - for (unsigned int j = 0; j < _configuration.size(); j++) { - const Node<std::string>& s = _configuration[j]; - if (isDescendant(s, domain)) { - statesToExit.push_back(s); - } - } - } - } -#if VERBOSE - std::cout << "computeExitSet: "; - for (size_t i = 0; i < statesToExit.size(); i++) { - std::cout << ATTR_CAST(statesToExit[i], "id") << " "; - } - std::cout << std::endl; -#endif - - return statesToExit; -} - -Arabica::XPath::NodeSet<std::string> InterpreterRC::computeExitSet(const Arabica::DOM::Node<std::string>& transition) { -// if (_exitSet.find(transition) != _exitSet.end()) // speed up removeConflicting -// return _exitSet[transition]; - - Arabica::XPath::NodeSet<std::string> transitions; - transitions.push_back(transition); - - Arabica::XPath::NodeSet<std::string> exitSet = computeExitSet(transitions); - //_exitSet[transition] = exitSet; - -#if 0 - std::cerr << "Exit set for transition '" << transition << "': " << std::endl; - for (size_t i = 0; i < exitSet.size(); i++) { - std::cerr << ATTR_CAST(exitSet[i], "id") << std::endl << "----" << std::endl; - } - std::cerr << std::endl; -#endif - return exitSet; -} - -void InterpreterRC::enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { - NodeSet<std::string> statesToEnter; - NodeSet<std::string> statesForDefaultEntry; - // initialize the temporary table for default content in history states - std::map<std::string, Arabica::DOM::Node<std::string> > defaultHistoryContent; - - computeEntrySet(enabledTransitions, statesToEnter, statesForDefaultEntry, defaultHistoryContent); - statesToEnter.to_document_order(); - -#if VERBOSE_STATE_SELECTION - std::cout << "enterStates: "; - for (size_t i = 0; i < statesToEnter.size(); i++) { - std::cout << ATTR_CAST(statesToEnter[i], "id") << " "; - } - std::cout << std::endl; -#endif - - for (size_t i = 0; i < statesToEnter.size(); i++) { - Element<std::string> s = (Element<std::string>)statesToEnter[i]; - - USCXML_MONITOR_CALLBACK3(beforeEnteringState, s, i + 1 < statesToEnter.size()) - - _configuration.push_back(s); - _statesToInvoke.push_back(s); - - if (_binding == LATE && !isMember(s, _alreadyEntered)) { - NodeSet<std::string> dataModelElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", s); - if(dataModelElems.size() > 0 && _dataModel) { - Arabica::XPath::NodeSet<std::string> dataElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "data", dataModelElems[0]); - for (size_t j = 0; j < dataElems.size(); j++) { - if (dataElems[j].getNodeType() == Node_base::ELEMENT_NODE) - initializeData(Element<std::string>(dataElems[j])); - } - } - _alreadyEntered.push_back(s); - } - // execute onentry executable content - NodeSet<std::string> onEntryElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "onEntry", s); - executeContent(onEntryElems, false); - - if (isMember(s, statesForDefaultEntry)) { - // execute initial transition content for compound states - Arabica::XPath::NodeSet<std::string> transitions = _xpath.evaluate("" + _nsInfo.xpathPrefix + "initial/" + _nsInfo.xpathPrefix + "transition", s).asNodeSet(); - if (transitions.size() > 0) { - executeContent(transitions); - } - } - - USCXML_MONITOR_CALLBACK3(afterEnteringState, s, i + 1 < statesToEnter.size()) - - if (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))) { - // extension for flattened interpreters - NodeSet<std::string> invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", s); - for (unsigned int j = 0; j < invokes.size(); j++) { - Element<std::string> invokeElem = Element<std::string>(invokes[j]); - if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { - invoke(invokeElem); - } - } - - // extension for flattened SCXML documents, we will need an explicit uninvoke element - NodeSet<std::string> uninvokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "uninvoke", s); - for (size_t j = 0; j < uninvokes.size(); j++) { - Element<std::string> uninvokeElem = (Element<std::string>)uninvokes[j]; - cancelInvoke(uninvokeElem); - } - } - -// std::cout << "HIST?: " << ATTR(s, "id") << std::endl; - if (defaultHistoryContent.find(ATTR(s, "id")) != defaultHistoryContent.end()) { - executeContent(Element<std::string>(defaultHistoryContent[ATTR(s, "id")])); - } - - if (isFinal(s)) { - Element<std::string> parent = (Element<std::string>)s.getParentNode(); - - Arabica::DOM::Element<std::string> doneData; - Arabica::XPath::NodeSet<std::string> doneDatas = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "donedata", s); - if (doneDatas.size() > 0) { - // only process first donedata element - doneData = Element<std::string>(doneDatas[0]); - } - - if (parentIsScxmlState(s)) { - _topLevelFinalReached = true; - } else { - internalDoneSend(parent, doneData); - Element<std::string> grandParent = (Element<std::string>)parent.getParentNode(); - -// internalDoneSend(parent, Arabica::DOM::Element<std::string>()); - - if (isParallel(grandParent)) { - Arabica::XPath::NodeSet<std::string> childs = getChildStates(grandParent); - bool inFinalState = true; - for (size_t j = 0; j < childs.size(); j++) { - if (!isInFinalState(Element<std::string>(childs[j]))) { - inFinalState = false; - break; - } - } - if (inFinalState) { - internalDoneSend(grandParent, Arabica::DOM::Element<std::string>()); - } - } - } - } - } -} - -/* -procedure computeEntrySet(transitions, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - for t in transitions: - for s in t.target: - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - ancestor = getTransitionDomain(t) - for s in getEffectiveTargetStates(t)): - addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent) -*/ - -void InterpreterRC::computeEntrySet(const Arabica::XPath::NodeSet<std::string>& transitions, - NodeSet<std::string>& statesToEnter, - NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent) { - - // add all descendants in a dedicated first step - for (size_t i = 0; i < transitions.size(); i++) { - Element<std::string> t(transitions[i]); - - NodeSet<std::string> targets = getTargetStates(t); - for (size_t j = 0; j < targets.size(); j++) { - Element<std::string> s = Element<std::string>(targets[j]); - addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - } - - // only now add the ancestors - for (size_t i = 0; i < transitions.size(); i++) { - Element<std::string> t(transitions[i]); - Element<std::string> ancestor = Element<std::string>(getTransitionDomain(t)); - NodeSet<std::string> effectiveTargetStates = getEffectiveTargetStates(t); - - for (size_t j = 0; j < effectiveTargetStates.size(); j++) { - Element<std::string> s = Element<std::string>(effectiveTargetStates[j]); - addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - - } -} - -/* -function getEffectiveTargetStates(transition) - effectiveTargets = new OrderedSet() - for s in transition.target - if isHistoryState(s): - if historyValue[s.id]: - effectiveTargets.union(historyValue[s.id]) - else: - effectiveTargets.union(getEffectiveTargetStates(s.transition)) - else: - effectiveTargets.add(s) - return effectiveTargets -*/ - -Arabica::XPath::NodeSet<std::string> InterpreterRC::getEffectiveTargetStates(const Arabica::DOM::Element<std::string>& transition) { - NodeSet<std::string> effectiveTargets; - - NodeSet<std::string> targets; - if (isState(transition)) { - targets = getInitialStates(transition); - return targets; - } else { - targets = getTargetStates(transition); - } - - for (size_t j = 0; j < targets.size(); j++) { - Element<std::string> s = Element<std::string>(targets[j]); - if (isHistory(s)) { - if (_historyValue.find(ATTR(s, "id")) != _historyValue.end()) { - targets.push_back(_historyValue[ATTR(s, "id")]); - } else { - NodeSet<std::string> histTrans = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", s); - // TODO: what if there are many history transitions? - if (histTrans.size() > 0) - targets.push_back(getEffectiveTargetStates(Element<std::string>(histTrans[0]))); - } - } else { - effectiveTargets.push_back(s); - } - } - - return effectiveTargets; -} - -void InterpreterRC::computeEntrySet(const Arabica::DOM::Node<std::string>& transition, - NodeSet<std::string>& statesToEnter, - NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent) { - Arabica::XPath::NodeSet<std::string> transitions; - transitions.push_back(transition); - computeEntrySet(transitions, statesToEnter, statesForDefaultEntry, defaultHistoryContent); -} - - -/* -procedure addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry, defaultHistoryContent): - if isHistoryState(state): - if historyValue[state.id]: - for s in historyValue[state.id]: - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - else: - defaultHistoryContent[state.parent.id] = state.transition.content - for s in state.transition.target: - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - else: - statesToEnter.add(state) - if isCompoundState(state): - statesForDefaultEntry.add(state) - for s in getEffectiveTargetStates(state.initial.transition): - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - else: - if isParallelState(state): - for child in getChildStates(state): - if not statesToEnter.some(lambda s: isDescendant(s,child)): - addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent) -*/ -void InterpreterRC::addDescendantStatesToEnter(const Arabica::DOM::Element<std::string>& state, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent) { - -#if VERBOSE_STATE_SELECTION - std::cout << getPadding() << "addDescendantStatesToEnter: " << ATTR(state, "id") << std::endl; - padding++; -#endif - - if (isHistory(state)) { - - /* - if historyValue[state.id]: - for s in historyValue[state.id]: - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - else: - defaultHistoryContent[state.parent.id] = state.transition.content - for s in state.transition.target: - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - */ - std::string stateId = ATTR(state, "id"); - if (_historyValue.find(stateId) != _historyValue.end()) { - const Arabica::XPath::NodeSet<std::string>& historyValue = _historyValue[stateId]; - for (size_t i = 0; i < historyValue.size(); i++) { - const Element<std::string>& s = Element<std::string>(historyValue[i]); - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent); - addAncestorStatesToEnter(s, getParentState(s), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - - } else { - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", state); - if (transitions.size() > 0) { - // TODO: what if there are many history transitions? -// std::cout << "HIST: " << ATTR_CAST(getParentState(state), "id") << std::endl; - defaultHistoryContent[ATTR_CAST(getParentState(state), "id")] = transitions[0]; - } - - for (size_t i = 0; i < transitions.size(); i++) { - NodeSet<std::string> targets = getTargetStates(Element<std::string>(transitions[i])); - for (size_t j = 0; j < targets.size(); j++) { - const Element<std::string>& s = Element<std::string>(targets[i]); - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent); - addAncestorStatesToEnter(s, getParentState(s), statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - } - } - } else { - /* - statesToEnter.add(state) - if isCompoundState(state): - statesForDefaultEntry.add(state) - for s in getEffectiveTargetStates(state.initial.transition): - addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - else: - if isParallelState(state): - for child in getChildStates(state): - if not statesToEnter.some(lambda s: isDescendant(s,child)): - addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent) - */ - - if (!isMember(state, statesToEnter)) { // adding an existing element invalidates old reference -#if VERBOSE_STATE_SELECTION - std::cout << getPadding() << "adding: " << ATTR_CAST(state, "id") << std::endl; -#endif - statesToEnter.push_back(state); - } - - if (isCompound(state)) { - statesForDefaultEntry.push_back(state); - - // test 579 - initial leads to history still fails - NodeSet<std::string> targets = getEffectiveTargetStates(Element<std::string>(state)); - for (size_t i = 0; i < targets.size(); i++) { - const Element<std::string>& s = Element<std::string>(targets[i]); - addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - - for (size_t i = 0; i < targets.size(); i++) { - const Element<std::string>& s = Element<std::string>(targets[i]); - addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent); - } - - } else if(isParallel(state)) { - NodeSet<std::string> childStates = getChildStates(state); - - for (size_t i = 0; i < childStates.size(); i++) { - const Element<std::string>& child = Element<std::string>(childStates[i]); - - for (size_t j = 0; j < statesToEnter.size(); j++) { - const Node<std::string>& s = statesToEnter[j]; - if (isDescendant(s, child)) { - goto BREAK_LOOP; - } - - } - addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent); -BREAK_LOOP: - ; - } - } - } - padding--; -} - -/* -procedure addAncestorStatesToEnter(state, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent) - for anc in getProperAncestors(state,ancestor): - statesToEnter.add(anc) - if isParallelState(anc): - for child in getChildStates(anc): - if not statesToEnter.some(lambda s: isDescendant(s,child)): - addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent) -*/ -void InterpreterRC::addAncestorStatesToEnter(const Arabica::DOM::Element<std::string>& state, - const Arabica::DOM::Element<std::string>& ancestor, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent) { - -#if VERBOSE_STATE_SELECTION - std::cout << getPadding() << "addAncestorStatesToEnter: " << ATTR(state, "id") << " - " << ATTR(ancestor, "id") << std::endl; - padding++; -#endif - - NodeSet<std::string> ancestors = getProperAncestors(state, ancestor); - for (size_t i = 0; i < ancestors.size(); i++) { - const Node<std::string>& anc = ancestors[i]; -#if VERBOSE_STATE_SELECTION - std::cout << getPadding() << "adding: " << ATTR_CAST(anc, "id") << std::endl; -#endif - - statesToEnter.push_back(anc); - - if (isParallel(Element<std::string>(anc))) { - NodeSet<std::string> childStates = getChildStates(anc); - for (size_t j = 0; j < childStates.size(); j++) { - const Element<std::string>& child = Element<std::string>(childStates[j]); - for (size_t k = 0; k < statesToEnter.size(); k++) { - const Node<std::string>& s = statesToEnter[k]; - if (isDescendant(s, child)) { - goto BREAK_LOOP; - } - } - addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent); -BREAK_LOOP: - ; - } - } - } - padding--; -} - -/* -function getTransitionDomain(t) - tstates = getEffectiveTargetStates(t) - if not tstates: - return null - elif t.type == "internal" and isCompoundState(t.source) and tstates.every(lambda s: isDescendant(s,t.source)): - return t.source - else: - return findLCCA([t.source].append(tstates)) -*/ -Arabica::DOM::Node<std::string> InterpreterRC::getTransitionDomain(const Arabica::DOM::Element<std::string>& transition) { - NodeSet<std::string> tStates = getEffectiveTargetStates(transition); - if (tStates.size() == 0) { - return Arabica::DOM::Node<std::string>(); // null - } - std::string transitionType = (HAS_ATTR(transition, "type") ? ATTR(transition, "type") : "external"); - Node<std::string> source = getSourceState(transition); - - if (iequals(transitionType, "internal") && isCompound(Element<std::string>(source))) { - for (size_t i = 0; i < tStates.size(); i++) { - const Node<std::string>& s = tStates[i]; - if (!isDescendant(s, source)) - goto BREAK_LOOP; - } - return source; - } - -BREAK_LOOP: - Arabica::XPath::NodeSet<std::string> states; - states.push_back(source); - states.push_back(tStates); - return findLCCA(states); -} - -void InterpreterRC::handleDOMEvent(Arabica::DOM::Events::Event<std::string>& event) { - InterpreterImpl::handleDOMEvent(event); - - if (event.getType().compare("DOMAttrModified") == 0) // we do not care about attributes - return; - -// Node<std::string> target = Arabica::DOM::Node<std::string>(event.getTarget()); -// NodeSet<std::string> transitions = DOMUtils::DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", target, true); -// if (transitions.size() > 0) - _exitSet.clear(); - -} -}
\ No newline at end of file diff --git a/src/uscxml/interpreter/InterpreterRC.h b/src/uscxml/interpreter/InterpreterRC.h deleted file mode 100644 index 36aca3d..0000000 --- a/src/uscxml/interpreter/InterpreterRC.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @file - * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see <http://www.opensource.org/licenses/bsd-license>. - * @endcond - */ - -#ifndef INTERPRETERRC_H_WLJEI019 -#define INTERPRETERRC_H_WLJEI019 - -#include "uscxml/Interpreter.h" - -namespace uscxml { - -class InterpreterRC : public InterpreterImpl { -protected: - void enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); - void exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); - Arabica::XPath::NodeSet<std::string> removeConflictingTransitions(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); - - bool hasIntersection(const Arabica::XPath::NodeSet<std::string>& nodeSet1, const Arabica::XPath::NodeSet<std::string>& nodeSet2); - - Arabica::XPath::NodeSet<std::string> computeExitSet(const Arabica::XPath::NodeSet<std::string>& transitions); - Arabica::XPath::NodeSet<std::string> computeExitSet(const Arabica::DOM::Node<std::string>& transition); - - void computeEntrySet(const Arabica::XPath::NodeSet<std::string>& transitions, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent); - void computeEntrySet(const Arabica::DOM::Node<std::string>& transition, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent); - - Arabica::DOM::Node<std::string> getTransitionDomain(const Arabica::DOM::Element<std::string>& transition); - Arabica::XPath::NodeSet<std::string> getEffectiveTargetStates(const Arabica::DOM::Element<std::string>& transition); - - void addDescendantStatesToEnter(const Arabica::DOM::Element<std::string>& state, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent); - - void addAncestorStatesToEnter(const Arabica::DOM::Element<std::string>& state, - const Arabica::DOM::Element<std::string>& ancestor, - Arabica::XPath::NodeSet<std::string>& statesToEnter, - Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry, - std::map<std::string, Arabica::DOM::Node<std::string> >& defaultHistoryContent); - virtual void handleDOMEvent(Arabica::DOM::Events::Event<std::string>& event); - -private: - std::map<Arabica::DOM::Node<std::string>, Arabica::XPath::NodeSet<std::string> > _exitSet; -}; - -} - -#endif /* end of include guard: INTERPRETERRC_H_WLJEI019 */ diff --git a/src/uscxml/interpreter/MicroStepFast.cpp b/src/uscxml/interpreter/MicroStepFast.cpp new file mode 100644 index 0000000..fbddbc9 --- /dev/null +++ b/src/uscxml/interpreter/MicroStepFast.cpp @@ -0,0 +1,1149 @@ +/** + * @file + * @author 2012-2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#undef USCXML_VERBOSE + +#include "MicroStepFast.h" +#include "uscxml/util/DOM.h" +#include "uscxml/util/String.h" +#include "uscxml/util/Predicates.h" +#include "uscxml/util/Convenience.h" +#include "uscxml/interpreter/InterpreterMonitor.h" + +#include <easylogging++.h> + +#define BIT_ANY_SET(b) (!b.none()) +#define BIT_HAS(idx, bitset) (bitset[idx]) +#define BIT_HAS_AND(bitset1, bitset2) bitset1.intersects(bitset2) +#define BIT_SET_AT(idx, bitset) bitset[idx] = true; +#define BIT_CLEAR(idx, bitset) bitset[idx] = false; + +#define USCXML_GET_TRANS(i) (*_transitions[i]) +#define USCXML_GET_STATE(i) (*_states[i]) + +#define USCXML_CTX_PRISTINE 0x00 +#define USCXML_CTX_SPONTANEOUS 0x01 +#define USCXML_CTX_INITIALIZED 0x02 +#define USCXML_CTX_TOP_LEVEL_FINAL 0x04 +#define USCXML_CTX_TRANSITION_FOUND 0x08 +#define USCXML_CTX_FINISHED 0x10 +#define USCXML_CTX_STABLE 0x20 // only needed to signal onStable once + +#define USCXML_TRANS_SPONTANEOUS 0x01 +#define USCXML_TRANS_TARGETLESS 0x02 +#define USCXML_TRANS_INTERNAL 0x04 +#define USCXML_TRANS_HISTORY 0x08 +#define USCXML_TRANS_INITIAL 0x10 + +#define USCXML_STATE_ATOMIC 0x01 +#define USCXML_STATE_PARALLEL 0x02 +#define USCXML_STATE_COMPOUND 0x03 +#define USCXML_STATE_FINAL 0x04 +#define USCXML_STATE_HISTORY_DEEP 0x05 +#define USCXML_STATE_HISTORY_SHALLOW 0x06 +#define USCXML_STATE_INITIAL 0x07 +#define USCXML_STATE_HAS_HISTORY 0x80 /* highest bit */ +#define USCXML_STATE_MASK(t) (t & 0x7F) /* mask highest bit */ + +#define USCXML_NUMBER_STATES _states.size() +#define USCXML_NUMBER_TRANS _transitions.size() + +#ifdef __GNUC__ +# define likely(x) (__builtin_expect(!!(x), 1)) +# define unlikely(x) (__builtin_expect(!!(x), 0)) +#else +# define likely(x) (x) +# define unlikely(x) (x) +#endif + +namespace uscxml { + +using namespace xercesc; + +MicroStepFast::MicroStepFast(MicroStepCallbacks* callbacks) + : MicroStepImpl(callbacks), _flags(USCXML_CTX_PRISTINE), _isInitialized(false), _isCancelled(false) { +} + +MicroStepFast::~MicroStepFast() { + for (size_t i = 0; i < _states.size(); i++) { + delete(_states[i]); + } + for (size_t i = 0; i < _transitions.size(); i++) { + delete(_transitions[i]); + } +} + +void MicroStepFast::resortStates(DOMNode* node, const X& xmlPrefix) { + if (node->getNodeType() != DOMNode::ELEMENT_NODE) + return; + + /** + initials + deep histories + shallow histories + everything else + */ + + DOMElement* element = dynamic_cast<DOMElement*>(node); + + // shallow history states to top + DOMNode* child = element->getFirstChild(); + while(child) { + resortStates(child, xmlPrefix); + if (child->getNodeType() == DOMNode::ELEMENT_NODE && + TAGNAME_CAST(child) == xmlPrefix.str() + "history" && + (!HAS_ATTR(element, "type") || iequals(ATTR(element, "type"), "shallow"))) { + + DOMNode* tmp = child->getNextSibling(); + if (child != element->getFirstChild()) { + element->insertBefore(child, element->getFirstChild()); + } + child = tmp; + } else { + child = child->getNextSibling(); + } + } + + // deep history states to top + child = element->getFirstChild(); + while(child) { + resortStates(child, xmlPrefix); + if (child->getNodeType() == DOMNode::ELEMENT_NODE && + TAGNAME_CAST(child) == xmlPrefix.str() + "history" && + HAS_ATTR(element, "type") && + iequals(ATTR(element, "type"), "deep")) { + + DOMNode* tmp = child->getNextSibling(); + if (child != element->getFirstChild()) { + element->insertBefore(child, element->getFirstChild()); + } + child = tmp; + } else { + child = child->getNextSibling(); + } + } + + // initial states on top of histories even + child = element->getFirstChild(); + while(child) { + resortStates(child, xmlPrefix); + if (child->getNodeType() == DOMNode::ELEMENT_NODE && LOCALNAME_CAST(child) == "initial") { + DOMNode* tmp = child->getNextSibling(); + if (child != element->getFirstChild()) { + element->insertBefore(child, element->getFirstChild()); + } + child = tmp; + } else { + child = child->getNextSibling(); + } + } +} + +void MicroStepFast::init(xercesc::DOMElement* scxml) { + + _scxml = scxml; + _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); + _xmlPrefix = _scxml->getPrefix(); + _xmlNS = _scxml->getNamespaceURI(); + if (_xmlPrefix) { + _xmlPrefix = std::string(_xmlPrefix) + ":"; + } + + resortStates(_scxml, _xmlPrefix); + + // TODO: https://github.com/tklab-tud/uscxml/blob/master/src/uscxml/transform/ChartToC.cpp#L244 + + + /** -- All things states -- */ + + std::set<std::string> elements; + elements.insert(_xmlPrefix.str() + "state"); + elements.insert(_xmlPrefix.str() + "parallel"); + elements.insert(_xmlPrefix.str() + "scxml"); + elements.insert(_xmlPrefix.str() + "initial"); + elements.insert(_xmlPrefix.str() + "final"); + elements.insert(_xmlPrefix.str() + "history"); + + std::list<xercesc::DOMElement*> tmp; + size_t i, j; + + tmp = DOMUtils::inDocumentOrder(elements, _scxml); + _states.resize(tmp.size()); + _configuration.resize(tmp.size()); + _history.resize(tmp.size()); + _initializedData.resize(tmp.size()); + _invocations.resize(tmp.size()); + + for (i = 0; i < _states.size(); i++) { + _states[i] = new State(); + _states[i]->documentOrder = i; + _states[i]->element = tmp.front(); + _states[i]->element->setUserData(X("uscxmlState"), _states[i], NULL); + _states[i]->completion.resize(_states.size()); + _states[i]->ancestors.resize(_states.size()); + _states[i]->children.resize(_states.size()); + tmp.pop_front(); + } + assert(tmp.size() == 0); + + if (_binding == Binding::EARLY && _states.size() > 0) { + // add all data elements to the first state + std::list<DOMElement*> dataModels = DOMUtils::filterChildElements(_xmlPrefix.str() + "datamodel", _states[0]->element, true); + dataModels.erase(std::remove_if(dataModels.begin(), + dataModels.end(), + [](DOMElement* elem) { + return isInEmbeddedDocument(elem); + }), + dataModels.end()); + + _states[0]->data = DOMUtils::filterChildElements(_xmlPrefix.str() + "data", dataModels, false); + } + + for (i = 0; i < _states.size(); i++) { + // collect states with an id attribute + if (HAS_ATTR(_states[i]->element, "id")) { + _stateIds[ATTR(_states[i]->element, "id")] = i; + } + + // check for executable content and datamodels + if (_states[i]->element->getChildElementCount() > 0) { + _states[i]->onEntry = DOMUtils::filterChildElements(_xmlPrefix.str() + "onentry", _states[i]->element); + _states[i]->onExit = DOMUtils::filterChildElements(_xmlPrefix.str() + "onexit", _states[i]->element); + _states[i]->invoke = DOMUtils::filterChildElements(_xmlPrefix.str() + "invoke", _states[i]->element); + + if (i == 0) { + // have global scripts as onentry of <scxml> + _states[i]->onEntry = DOMUtils::filterChildElements(_xmlPrefix.str() + "script", _states[i]->element, false); + } + + std::list<DOMElement*> doneDatas = DOMUtils::filterChildElements(_xmlPrefix.str() + "donedata", _states[i]->element); + if (doneDatas.size() > 0) { + _states[i]->doneData = doneDatas.front(); + } + + if (_binding == Binding::LATE) { + std::list<DOMElement*> dataModels = DOMUtils::filterChildElements(_xmlPrefix.str() + "datamodel", _states[i]->element); + if (dataModels.size() > 0) { + _states[i]->data = DOMUtils::filterChildElements(_xmlPrefix.str() + "data", dataModels, false); + } + } + } + + + // set the states type + if (false) { + } else if (iequals(TAGNAME(_states[i]->element), _xmlPrefix.str() + "initial")) { + _states[i]->type = USCXML_STATE_INITIAL; + } else if (isFinal(_states[i]->element)) { + _states[i]->type = USCXML_STATE_FINAL; + } else if (isHistory(_states[i]->element)) { + if (HAS_ATTR(_states[i]->element, "type") && iequals(ATTR(_states[i]->element, "type"), "deep")) { + _states[i]->type = USCXML_STATE_HISTORY_DEEP; + } else { + _states[i]->type = USCXML_STATE_HISTORY_SHALLOW; + } + } else if (isAtomic(_states[i]->element)) { + _states[i]->type = USCXML_STATE_ATOMIC; + } else if (isParallel(_states[i]->element)) { + _states[i]->type = USCXML_STATE_PARALLEL; + } else if (isCompound(_states[i]->element)) { + _states[i]->type = USCXML_STATE_COMPOUND; + } else { // <scxml> + _states[i]->type = USCXML_STATE_COMPOUND; + } + + // establish the states' completion + std::list<DOMElement*> completion = getCompletion(_states[i]->element); + for (j = 0; j < _states.size(); j++) { + if (!completion.empty() && _states[j]->element == completion.front()) { + _states[i]->completion[j] = true; + completion.pop_front(); + } else { + _states[i]->completion[j] = false; + } + } + assert(completion.size() == 0); + + // this is set when establishing the completion + if (_states[i]->element->getUserData(X("hasHistoryChild")) == _states[i]) { + _states[i]->type |= USCXML_STATE_HAS_HISTORY; + } + + // establish the states' ancestors + DOMNode* parent = _states[i]->element->getParentNode(); + if (parent && parent->getNodeType() == DOMNode::ELEMENT_NODE) { + State* uscxmlState = (State*)parent->getUserData(X("uscxmlState")); + // parent + _states[i]->parent = uscxmlState->documentOrder; + } + + while(parent && parent->getNodeType() == DOMNode::ELEMENT_NODE) { + State* uscxmlState = (State*)parent->getUserData(X("uscxmlState")); + + // ancestors + BIT_SET_AT(uscxmlState->documentOrder, _states[i]->ancestors); + + // children + BIT_SET_AT(i, uscxmlState->children); + parent = parent->getParentNode(); + } + } + + + /** -- All things transitions -- */ + + tmp = DOMUtils::inPostFixOrder(_xmlPrefix.str() + "transition", _scxml); + _transitions.resize(tmp.size()); + + for (i = 0; i < _transitions.size(); i++) { + _transitions[i] = new Transition(); + _transitions[i]->element = tmp.front(); + _transitions[i]->conflicts.resize(_transitions.size()); + _transitions[i]->exitSet.resize(_states.size()); + _transitions[i]->target.resize(_states.size()); + tmp.pop_front(); + } + + for (i = 0; i < _transitions.size(); i++) { + + // establish the transitions' exit set + std::list<DOMElement*> exitList = getExitSet(_transitions[i]->element, _scxml); + for (j = 0; j < _states.size(); j++) { + if (!exitList.empty() && _states[j]->element == exitList.front()) { + _transitions[i]->exitSet[j] = true; + exitList.pop_front(); + } else { + _transitions[i]->exitSet[j] = false; + } + } + assert(exitList.size() == 0); + + // establish the transitions' conflict set + for (j = 0; j < _transitions.size(); j++) { + if (conflicts(_transitions[i]->element, _transitions[j]->element, _scxml)) { + _transitions[i]->conflicts[j] = true; + } else { + _transitions[i]->conflicts[j] = false; + } + } + + // establish the transitions' target set + std::list<std::string> targets = tokenize(ATTR(_transitions[i]->element, "target")); + for (auto tIter = targets.begin(); tIter != targets.end(); tIter++) { + if (_stateIds.find(*tIter) != _stateIds.end()) { + _transitions[i]->target[_stateIds[*tIter]] = true; + } + } + + // the transition's source + State* uscxmlState = (State*)(_transitions[i]->element->getParentNode()->getUserData(X("uscxmlState"))); + _transitions[i]->source = uscxmlState->documentOrder; + + + // the transition's type + if (!HAS_ATTR(_transitions[i]->element, "target")) { + _transitions[i]->type |= USCXML_TRANS_TARGETLESS; + } + + if (HAS_ATTR(_transitions[i]->element, "type") && iequals(ATTR(_transitions[i]->element, "type"), "internal")) { + _transitions[i]->type |= USCXML_TRANS_INTERNAL; + } + + if (!HAS_ATTR(_transitions[i]->element, "event")) { + _transitions[i]->type |= USCXML_TRANS_SPONTANEOUS; + } + + if (iequals(TAGNAME_CAST(_transitions[i]->element->getParentNode()), _xmlPrefix.str() + "history")) { + _transitions[i]->type |= USCXML_TRANS_HISTORY; + } + + if (iequals(TAGNAME_CAST(_transitions[i]->element->getParentNode()), _xmlPrefix.str() + "initial")) { + _transitions[i]->type |= USCXML_TRANS_INITIAL; + } + + + // the transitions event and condition + _transitions[i]->event = (HAS_ATTR(_transitions[i]->element, "event") ? ATTR(_transitions[i]->element, "event") : ""); + _transitions[i]->cond = (HAS_ATTR(_transitions[i]->element, "cond") ? ATTR(_transitions[i]->element, "cond") : ""); + + // is there executable content? + if (_transitions[i]->element->getChildElementCount() > 0) { + _transitions[i]->onTrans = _transitions[i]->element; + } + + } + _isInitialized = true; +} + +void MicroStepFast::markAsCancelled() { + _isCancelled = true; +} + +InterpreterState MicroStepFast::step(bool blocking) { + if (!_isInitialized) { + init(_scxml); + return USCXML_INITIALIZED; + } + + size_t i, j, k; + + boost::dynamic_bitset<> exitSet(_states.size(), false); + boost::dynamic_bitset<> entrySet(_states.size(), false); + boost::dynamic_bitset<> targetSet(_states.size(), false); + boost::dynamic_bitset<> tmpStates(_states.size(), false); + + boost::dynamic_bitset<> conflicts(_transitions.size(), false); + boost::dynamic_bitset<> transSet(_transitions.size(), false); + +#ifdef USCXML_VERBOSE + std::cerr << "Config: "; + printStateNames(_configuration); +#endif + + if (_flags & USCXML_CTX_FINISHED) + return USCXML_FINISHED; + + if (_flags & USCXML_CTX_TOP_LEVEL_FINAL) { + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), beforeCompletion); + + /* exit all remaining states */ + i = USCXML_NUMBER_STATES; + while(i-- > 0) { + if (BIT_HAS(i, _configuration)) { + /* call all on exit handlers */ + for (auto exitIter = USCXML_GET_STATE(i).onExit.begin(); exitIter != USCXML_GET_STATE(i).onExit.end(); exitIter++) { + try { + _callbacks->process(*exitIter); + } catch (...) { + // do nothing and continue with next block + } + } + /* Leave configuration intact */ +// BIT_CLEAR(i, _configuration); + } + + if (BIT_HAS(i, _invocations)) { + /* cancel all invokers */ + if (USCXML_GET_STATE(i).invoke.size() > 0) { + for (auto invIter = USCXML_GET_STATE(i).invoke.begin(); invIter != USCXML_GET_STATE(i).invoke.end(); invIter++) { + _callbacks->uninvoke(*invIter); + } + } + BIT_CLEAR(i, _invocations); + } + } + + _flags |= USCXML_CTX_FINISHED; + + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), afterCompletion); + + return USCXML_FINISHED; + } + + + if (_flags == USCXML_CTX_PRISTINE) { + + targetSet |= USCXML_GET_STATE(0).completion; + _flags |= USCXML_CTX_SPONTANEOUS | USCXML_CTX_INITIALIZED; + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), beforeMicroStep); + + goto ESTABLISH_ENTRYSET; + } + + if (_flags & USCXML_CTX_SPONTANEOUS) { + _event = Event(); + goto SELECT_TRANSITIONS; + } + + + if ((_event = _callbacks->dequeueInternal())) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeProcessingEvent, _event); + goto SELECT_TRANSITIONS; + } + + /* manage invocations */ + for (i = 0; i < USCXML_NUMBER_STATES; i++) { + /* uninvoke */ + if (!BIT_HAS(i, _configuration) && BIT_HAS(i, _invocations)) { + if (USCXML_GET_STATE(i).invoke.size() > 0) { + for (auto invIter = USCXML_GET_STATE(i).invoke.begin(); invIter != USCXML_GET_STATE(i).invoke.end(); invIter++) { + _callbacks->uninvoke(*invIter); + } + } + BIT_CLEAR(i, _invocations) + } + /* invoke */ + if (BIT_HAS(i, _configuration) && !BIT_HAS(i, _invocations)) { + if (USCXML_GET_STATE(i).invoke.size() > 0) { + for (auto invIter = USCXML_GET_STATE(i).invoke.begin(); invIter != USCXML_GET_STATE(i).invoke.end(); invIter++) { + try { + _callbacks->invoke(*invIter); + } catch (...) { + } + } + } + BIT_SET_AT(i, _invocations) + } + } + + // we dequeued all internal events and ought to signal stable configuration + if (!(_flags & USCXML_CTX_STABLE)) { + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), onStableConfiguration); + _microstepConfigurations.clear(); + _flags |= USCXML_CTX_STABLE; + } + + if ((_event = _callbacks->dequeueExternal(blocking))) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeProcessingEvent, _event); + goto SELECT_TRANSITIONS; + } + + if (_isCancelled) { + // finalize and exit + _flags |= USCXML_CTX_TOP_LEVEL_FINAL; + return USCXML_CANCELLED; + } + +// if (blocking) // we received the empty event to unblock +// return USCXML_IDLE; // we return IDLE nevertheless + + return USCXML_IDLE; + +SELECT_TRANSITIONS: + + // we read an event - unset stable to signal onstable again later + _flags &= ~USCXML_CTX_STABLE; + + for (i = 0; i < _transitions.size(); i++) { + /* never select history or initial transitions automatically */ + if unlikely(USCXML_GET_TRANS(i).type & (USCXML_TRANS_HISTORY | USCXML_TRANS_INITIAL)) + continue; + + /* is the transition active? */ + if (BIT_HAS(USCXML_GET_TRANS(i).source, _configuration)) { + /* is it non-conflicting? */ + if (!BIT_HAS(i, conflicts)) { + /* is it spontaneous with an event or vice versa? */ + if ((USCXML_GET_TRANS(i).event.size() == 0 && !_event) || + (USCXML_GET_TRANS(i).event.size() != 0 && _event)) { + /* is it enabled? */ + if ((!_event || _callbacks->isMatched(_event, USCXML_GET_TRANS(i).event)) && + (USCXML_GET_TRANS(i).cond.size() == 0 || _callbacks->isTrue(USCXML_GET_TRANS(i).cond))) { + + /* remember that we found a transition */ + _flags |= USCXML_CTX_TRANSITION_FOUND; + + /* transitions that are pre-empted */ + conflicts |= USCXML_GET_TRANS(i).conflicts; + + /* states that are directly targeted (resolve as entry-set later) */ + targetSet |= USCXML_GET_TRANS(i).target; + + /* states that will be left */ + exitSet |= USCXML_GET_TRANS(i).exitSet; + + BIT_SET_AT(i, transSet); + } + } + } + } + } + +#ifdef USCXML_VERBOSE + std::cerr << "Complete Exit: "; + printStateNames(exitSet); +#endif + + exitSet &= _configuration; + + if (_flags & USCXML_CTX_TRANSITION_FOUND) { + // trigger more sppontaneuous transitions + _flags |= USCXML_CTX_SPONTANEOUS; + _flags &= ~USCXML_CTX_TRANSITION_FOUND; + } else { + // spontaneuous transitions are exhausted + _flags &= ~USCXML_CTX_SPONTANEOUS; + return USCXML_MACROSTEPPED; + } + + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), beforeMicroStep); + +#ifdef USCXML_VERBOSE + std::cerr << "Targets: "; + printStateNames(targetSet); +#endif + +#ifdef USCXML_VERBOSE + std::cerr << "Exiting: "; + printStateNames(exitSet); +#endif + +#ifdef USCXML_VERBOSE + std::cerr << "History: "; + printStateNames(_history); +#endif + + + /* REMEMBER_HISTORY: */ + for (i = 0; i < _states.size(); i++) { + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_SHALLOW || + USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_DEEP) { + /* a history state whose parent is about to be exited */ + if unlikely(BIT_HAS(USCXML_GET_STATE(i).parent, exitSet)) { + tmpStates = USCXML_GET_STATE(i).completion; + + /* set those states who were enabled */ + tmpStates &= _configuration; + + /* clear current history with completion mask */ + _history &= ~(USCXML_GET_STATE(i).completion); + + /* set history */ + _history |= tmpStates; + + } + } + } + +ESTABLISH_ENTRYSET: + /* calculate new entry set */ + entrySet = targetSet; + + /* iterate for ancestors */ + i = entrySet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + entrySet |= USCXML_GET_STATE(i).ancestors; + i = entrySet.find_next(i); + } + + /* iterate for descendants */ + i = entrySet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + + + switch (USCXML_STATE_MASK(USCXML_GET_STATE(i).type)) { + case USCXML_STATE_FINAL: + case USCXML_STATE_ATOMIC: + break; + + case USCXML_STATE_PARALLEL: { + entrySet |= USCXML_GET_STATE(i).completion; + break; + } + + case USCXML_STATE_HISTORY_SHALLOW: + case USCXML_STATE_HISTORY_DEEP: { + if (!BIT_HAS_AND(USCXML_GET_STATE(i).completion, _history) && + !BIT_HAS(USCXML_GET_STATE(i).parent, _configuration)) { + + /* nothing set for history, look for a default transition */ + for (j = 0; j < _transitions.size(); j++) { + if unlikely(USCXML_GET_TRANS(j).source == i) { + entrySet |= USCXML_GET_TRANS(j).target; + + if(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_DEEP && + !BIT_HAS_AND(USCXML_GET_TRANS(j).target, USCXML_GET_STATE(i).children)) { + for (k = i + 1; k < _states.size(); k++) { + if (BIT_HAS(k, USCXML_GET_TRANS(j).target)) { + entrySet |= USCXML_GET_STATE(k).ancestors; + break; + } + } + } + BIT_SET_AT(j, transSet); + break; + } + /* Note: SCXML mandates every history to have a transition! */ + } + } else { + tmpStates = USCXML_GET_STATE(i).completion; + tmpStates &= _history; + entrySet |= tmpStates; + + if (USCXML_GET_STATE(i).type == (USCXML_STATE_HAS_HISTORY | USCXML_STATE_HISTORY_DEEP)) { + /* a deep history state with nested histories -> more completion */ + for (j = i + 1; j < USCXML_NUMBER_STATES; j++) { + if (BIT_HAS(j, USCXML_GET_STATE(i).completion) && + BIT_HAS(j, entrySet) && + (USCXML_GET_STATE(j).type & USCXML_STATE_HAS_HISTORY)) { + for (k = j + 1; k < USCXML_NUMBER_STATES; k++) { + /* add nested history to entry_set */ + if ((USCXML_STATE_MASK(USCXML_GET_STATE(k).type) == USCXML_STATE_HISTORY_DEEP || + USCXML_STATE_MASK(USCXML_GET_STATE(k).type) == USCXML_STATE_HISTORY_SHALLOW) && + BIT_HAS(k, USCXML_GET_STATE(j).children)) { + /* a nested history state */ + BIT_SET_AT(k, entrySet); + } + } + } + } + } + } + break; + } + + case USCXML_STATE_INITIAL: { + for (j = 0; j < USCXML_NUMBER_TRANS; j++) { + if (USCXML_GET_TRANS(j).source == i) { + BIT_SET_AT(j, transSet); + BIT_CLEAR(i, entrySet); + entrySet |= USCXML_GET_TRANS(j).target; + for (k = i + 1; k < USCXML_NUMBER_STATES; k++) { + if (BIT_HAS(k, USCXML_GET_TRANS(j).target)) { + entrySet |= USCXML_GET_STATE(k).ancestors; + } + } + } + } + break; + } + case USCXML_STATE_COMPOUND: { /* we need to check whether one child is already in entry_set */ + if (!BIT_HAS_AND(entrySet, USCXML_GET_STATE(i).children) && + (!BIT_HAS_AND(_configuration, USCXML_GET_STATE(i).children) || + BIT_HAS_AND(exitSet, USCXML_GET_STATE(i).children))) { + entrySet |= USCXML_GET_STATE(i).completion; + if (!BIT_HAS_AND(USCXML_GET_STATE(i).completion, USCXML_GET_STATE(i).children)) { + /* deep completion */ + for (j = i + 1; j < USCXML_NUMBER_STATES; j++) { + if (BIT_HAS(j, USCXML_GET_STATE(i).completion)) { + entrySet |= USCXML_GET_STATE(j).ancestors; + break; /* completion of compound is single state */ + } + } + } + } + break; + } + } + i = entrySet.find_next(i); + + } + + +#ifdef USCXML_VERBOSE + std::cerr << "Transitions: " << transSet << std::endl; +#endif + + /* EXIT_STATES: */ + /* we cannot use find_first due to ordering */ + i = USCXML_NUMBER_STATES; + while(i-- > 0) { + if (BIT_HAS(i, exitSet) && BIT_HAS(i, _configuration)) { + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeExitingState, USCXML_GET_STATE(i).element); + + /* call all on exit handlers */ + for (auto exitIter = USCXML_GET_STATE(i).onExit.begin(); exitIter != USCXML_GET_STATE(i).onExit.end(); exitIter++) { + try { + _callbacks->process(*exitIter); + } catch (...) { + // do nothing and continue with next block + } + } + BIT_CLEAR(i, _configuration); + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterExitingState, USCXML_GET_STATE(i).element); + + } + } + + /* TAKE_TRANSITIONS: */ + i = transSet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + if ((USCXML_GET_TRANS(i).type & (USCXML_TRANS_HISTORY | USCXML_TRANS_INITIAL)) == 0) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeTakingTransition, USCXML_GET_TRANS(i).element); + + if (USCXML_GET_TRANS(i).onTrans != NULL) { + + /* call executable content in non-history, non-initial transition */ + try { + _callbacks->process(USCXML_GET_TRANS(i).onTrans); + } catch (...) { + // do nothing and continue with next block + } + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterTakingTransition, USCXML_GET_TRANS(i).element); + + } + i = transSet.find_next(i); + } + +#ifdef USCXML_VERBOSE + std::cerr << "Entering: "; + printStateNames(entrySet); +#endif + + + /* ENTER_STATES: */ + i = entrySet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + + if (BIT_HAS(i, _configuration)) { + // already active + i = entrySet.find_next(i); + continue; + } + + /* these are no proper states */ + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_DEEP || + USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_SHALLOW || + USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_INITIAL) { + i = entrySet.find_next(i); + continue; + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeEnteringState, USCXML_GET_STATE(i).element); + + BIT_SET_AT(i, _configuration); + + /* initialize data */ + if (!BIT_HAS(i, _initializedData)) { + for (auto dataIter = USCXML_GET_STATE(i).data.begin(); dataIter != USCXML_GET_STATE(i).data.end(); dataIter++) { + _callbacks->initData(*dataIter); + } + BIT_SET_AT(i, _initializedData); + } + + /* call all on entry handlers */ + for (auto entryIter = USCXML_GET_STATE(i).onEntry.begin(); entryIter != USCXML_GET_STATE(i).onEntry.end(); entryIter++) { + try { + _callbacks->process(*entryIter); + } catch (...) { + // do nothing and continue with next block + } + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterEnteringState, USCXML_GET_STATE(i).element); + + /* take history and initial transitions */ + for (j = 0; j < USCXML_NUMBER_TRANS; j++) { + if unlikely(BIT_HAS(j, transSet) && + (USCXML_GET_TRANS(j).type & (USCXML_TRANS_HISTORY | USCXML_TRANS_INITIAL)) && + USCXML_GET_STATE(USCXML_GET_TRANS(j).source).parent == i) { + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeTakingTransition, USCXML_GET_TRANS(j).element); + + /* call executable content in transition */ + if (USCXML_GET_TRANS(j).onTrans != NULL) { + try { + _callbacks->process(USCXML_GET_TRANS(j).onTrans); + } catch (...) { + // do nothing and continue with next block + } + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterTakingTransition, USCXML_GET_TRANS(j).element); + + } + } + + /* handle final states */ + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_FINAL) { + if unlikely(USCXML_GET_STATE(i).ancestors.count() == 1 && BIT_HAS(0, USCXML_GET_STATE(i).ancestors)) { + // only the topmost scxml is an ancestor + _flags |= USCXML_CTX_TOP_LEVEL_FINAL; + } else { + /* raise done event */ + _callbacks->raiseDoneEvent(USCXML_GET_STATE(USCXML_GET_STATE(i).parent).element, USCXML_GET_STATE(i).doneData); + } + + /** + * are we the last final state to leave a parallel state?: + * 1. Gather all parallel states in our ancestor chain + * 2. Find all states for which these parallels are ancestors + * 3. Iterate all active final states and remove their ancestors + * 4. If a state remains, not all children of a parallel are final + */ + for (j = 0; j < USCXML_NUMBER_STATES; j++) { + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(j).type) == USCXML_STATE_PARALLEL && + BIT_HAS(j, USCXML_GET_STATE(i).ancestors)) { + tmpStates.reset(); + k = _configuration.find_first(); + while (k != boost::dynamic_bitset<>::npos) { + if (BIT_HAS(j, USCXML_GET_STATE(k).ancestors)) { + if (USCXML_STATE_MASK(USCXML_GET_STATE(k).type) == USCXML_STATE_FINAL) { + tmpStates ^= USCXML_GET_STATE(k).ancestors; + } else { + BIT_SET_AT(k, tmpStates); + } + } + k = _configuration.find_next(k); + } + if (!tmpStates.any()) { + // raise done for state j + _callbacks->raiseDoneEvent(USCXML_GET_STATE(j).element, USCXML_GET_STATE(j).doneData); + } + } + } + } + } + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), afterMicroStep); + + // are we running in circles? + if (_microstepConfigurations.find(_configuration) != _microstepConfigurations.end()) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), + reportIssue, + InterpreterIssue("Reentering same configuration during microstep - possible endless loop", + NULL, + InterpreterIssue::USCXML_ISSUE_WARNING)); + } + _microstepConfigurations.insert(_configuration); + + return USCXML_MICROSTEPPED; +} + +void MicroStepFast::reset() { + _isCancelled = false; + _flags = USCXML_CTX_PRISTINE; + _configuration.reset(); + _history.reset(); + _initializedData.reset(); + _invocations.reset(); + +} + +bool MicroStepFast::isInState(const std::string& stateId) { +#ifdef USCXML_VERBOSE + printStateNames(_configuration); +#endif + if (_stateIds.find(stateId) == _stateIds.end()) + return false; + return _configuration[_stateIds[stateId]]; +} + +std::list<xercesc::DOMElement*> MicroStepFast::getConfiguration() { + std::list<xercesc::DOMElement*> config; + size_t i = _configuration.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + config.push_back(_states[i]->element); + i = _configuration.find_next(i); + } + return config; +} + + +std::list<DOMElement*> MicroStepFast::getHistoryCompletion(const DOMElement* history) { + std::set<std::string> elements; + elements.insert(_xmlPrefix.str() + "history"); + std::list<DOMElement*> histories = DOMUtils::inPostFixOrder(elements, _scxml); + + std::list<DOMElement*> covered; + std::list<DOMElement*> perParentcovered; + const DOMNode* parent = NULL; + + std::list<DOMElement*> completion; + + + if (parent != history->getParentNode()) { + covered.insert(covered.end(), perParentcovered.begin(), perParentcovered.end()); + perParentcovered.clear(); + parent = history->getParentNode(); + } + + bool deep = (HAS_ATTR(history, "type") && iequals(ATTR(history, "type"), "deep")); + + for (size_t j = 0; j < _states.size(); j++) { + if (_states[j]->element == history) + continue; + + if (DOMUtils::isDescendant((DOMNode*)_states[j]->element, history->getParentNode()) && isHistory(_states[j]->element)) { + ((DOMElement*)history)->setUserData(X("hasHistoryChild"), _states[j], NULL); + } + + if (DOMUtils::isMember(_states[j]->element, covered)) + continue; + + if (deep) { + if (DOMUtils::isDescendant(_states[j]->element, history->getParentNode()) && !isHistory(_states[j]->element)) { + completion.push_back(_states[j]->element); + } + } else { + if (_states[j]->element->getParentNode() == history->getParentNode() && !isHistory(_states[j]->element)) { + completion.push_back(_states[j]->element); + } + } + } + + return completion; +} + +#ifdef USCXML_VERBOSE +/** + * Print name of states contained in a (debugging). + */ +void MicroStepFast::printStateNames(const boost::dynamic_bitset<>& a) { + size_t i; + const char* seperator = ""; + for (i = 0; i < a.size(); i++) { + if (BIT_HAS(i, a)) { + std::cerr << seperator << (HAS_ATTR(USCXML_GET_STATE(i).element, X("id")) ? ATTR(USCXML_GET_STATE(i).element, X("id")) : "UNK"); + seperator = ", "; + } + } + std::cerr << std::endl; +} +#endif + +std::list<DOMElement*> MicroStepFast::getCompletion(const DOMElement* state) { + + if (isHistory(state)) { + // we already did in setHistoryCompletion + return getHistoryCompletion(state); + + } else if (isParallel(state)) { + return getChildStates(state); + + } else if (HAS_ATTR(state, "initial")) { + return getStates(tokenize(ATTR(state, "initial")), _scxml); + + } else { + std::list<DOMElement*> completion; + + std::list<DOMElement*> initElems = DOMUtils::filterChildElements(_xmlPrefix.str() + "initial", state); + if(initElems.size() > 0) { + // initial element is first child + completion.push_back(initElems.front()); + } else { + // first child state + DOMNodeList* children = state->getChildNodes(); + for (size_t i = 0; i < children->getLength(); i++) { + if (children->item(i)->getNodeType() != DOMNode::ELEMENT_NODE) + continue; + if (isState(dynamic_cast<DOMElement*>(children->item(i)))) { + completion.push_back(dynamic_cast<DOMElement*>(children->item(i))); + break; + } + } + } + return completion; + } + +} + + +#if 0 +/** + * See: http://www.w3.org/TR/scxml/#LegalStateConfigurations + */ +bool MicroStepFast::hasLegalConfiguration() { + + // The configuration contains exactly one child of the <scxml> element. + std::list<DOMElement*> scxmlChilds = getChildStates(_scxml, true); + DOMNode* foundScxmlChild; + for (auto sIter = scxmlChilds.begin(); sIter != scxmlChilds.end(); sIter++) { + DOMElement* state = *sIter; + if (isMember(state, config)) { + if (foundScxmlChild) { + LOG(ERROR) << "Invalid configuration: Multiple childs of scxml root are active '" << ATTR_CAST(foundScxmlChild, "id") << "' and '" << ATTR_CAST(scxmlChilds[i], "id") << "'"; + return false; + } + foundScxmlChild = scxmlChilds[i]; + } + } + if (!foundScxmlChild) { + LOG(ERROR) << "Invalid configuration: No childs of scxml root are active"; + + return false; + } + + // The configuration contains one or more atomic states. + bool foundAtomicState = false; + for (size_t i = 0; i < config.size(); i++) { + if (isAtomic(Element<std::string>(config[i]))) { + foundAtomicState = true; + break; + } + } + if (!foundAtomicState) { + LOG(ERROR) << "Invalid configuration: No atomic state is active"; + return false; + } + + // the configuration contains no history pseudo-states + for (size_t i = 0; i < config.size(); i++) { + if (isHistory(Element<std::string>(config[i]))) { + LOG(ERROR) << "Invalid configuration: history state " << ATTR_CAST(config[i], "id") << " is active"; + return false; + } + } + + + + // When the configuration contains an atomic state, it contains all of its <state> and <parallel> ancestors. + for (size_t i = 0; i < config.size(); i++) { + if (isAtomic(Element<std::string>(config[i]))) { + Node<std::string> parent = config[i]; + while(((parent = parent.getParentNode()) && parent.getNodeType() == Node_base::ELEMENT_NODE)) { + if (isState(Element<std::string>(parent)) && + (iequals(LOCALNAME(parent), "state") || + iequals(LOCALNAME(parent), "parallel"))) { + if (!isMember(parent, config)) { + LOG(ERROR) << "Invalid configuration: atomic state '" << ATTR_CAST(config[i], "id") << "' is active, but parent '" << ATTR_CAST(parent, "id") << "' is not"; + return false; + } + } + } + } + } + + // When the configuration contains a non-atomic <state>, it contains one and only one of the state's children + for (size_t i = 0; i < config.size(); i++) { + Element<std::string> configElem(config[i]); + if (!isAtomic(configElem) && !isParallel(configElem)) { + Node<std::string> foundChildState; + //std::cerr << config[i] << std::endl; + NodeSet<std::string> childs = getChildStates(config[i]); + for (size_t j = 0; j < childs.size(); j++) { + //std::cerr << childs[j] << std::endl; + if (isMember(childs[j], config)) { + if (foundChildState) { + LOG(ERROR) << "Invalid configuration: Multiple childs of compound '" << ATTR_CAST(config[i], "id") + << "' are active '" << ATTR_CAST(foundChildState, "id") << "' and '" << ATTR_CAST(childs[j], "id") << "'"; + return false; + } + foundChildState = childs[j]; + } + } + if (!foundChildState) { + LOG(ERROR) << "Invalid configuration: No childs of compound '" << ATTR_CAST(config[i], "id") << "' are active"; + return false; + } + } + } + + // If the configuration contains a <parallel> state, it contains all of its children + for (size_t i = 0; i < config.size(); i++) { + if (isParallel(Element<std::string>(config[i]))) { + NodeSet<std::string> childs = getChildStates(config[i]); + for (size_t j = 0; j < childs.size(); j++) { + if (!isMember(childs[j], config) && !isHistory(Element<std::string>(childs[j]))) { + LOG(ERROR) << "Invalid configuration: Not all children of parallel '" << ATTR_CAST(config[i], "id") << "' are active i.e. '" << ATTR_CAST(childs[j], "id") << "' is not"; + return false; + } + } + } + } + + // everything worked out fine! + return true; +} +#endif + +}
\ No newline at end of file diff --git a/src/uscxml/interpreter/MicroStepFast.h b/src/uscxml/interpreter/MicroStepFast.h new file mode 100644 index 0000000..c41be80 --- /dev/null +++ b/src/uscxml/interpreter/MicroStepFast.h @@ -0,0 +1,127 @@ +/** + * @file + * @author 2012-2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#ifndef INTERPRETERFAST_H_DA55E52B +#define INTERPRETERFAST_H_DA55E52B + +//#define USCXML_VERBOSE 1 + +#include <vector> +#include <set> +#include "MicroStepImpl.h" + +#include <boost/dynamic_bitset.hpp> + +namespace uscxml { + +class MicroStepFast : public MicroStepImpl { +public: + MicroStepFast(MicroStepCallbacks* callbacks); + virtual ~MicroStepFast(); + + virtual InterpreterState step(bool blocking); + virtual void reset(); + virtual bool isInState(const std::string& stateId); + virtual std::list<xercesc::DOMElement*> getConfiguration(); + void markAsCancelled(); + +protected: + class Transition { + public: + Transition() : element(NULL), source(0), onTrans(NULL), type(0) {} + + xercesc::DOMElement* element; + boost::dynamic_bitset<> conflicts; + boost::dynamic_bitset<> exitSet; + + uint32_t source; + boost::dynamic_bitset<> target; + + xercesc::DOMElement* onTrans; + + std::string event; + std::string cond; + + unsigned char type; + + }; + + class State { + public: + State() : element(NULL), parent(0), documentOrder(0), doneData(NULL), type(0) {} + + xercesc::DOMElement* element; + boost::dynamic_bitset<> completion; + boost::dynamic_bitset<> children; + boost::dynamic_bitset<> ancestors; + uint32_t parent; + uint32_t documentOrder; + + std::list<xercesc::DOMElement*> data; + std::list<xercesc::DOMElement*> invoke; + std::list<xercesc::DOMElement*> onEntry; + std::list<xercesc::DOMElement*> onExit; + xercesc::DOMElement* doneData; + + unsigned char type; + }; + + virtual void init(xercesc::DOMElement* scxml); + + std::list<xercesc::DOMElement*> getCompletion(const xercesc::DOMElement* state); + + unsigned char _flags; + std::map<std::string, int> _stateIds; + + std::vector<State*> _states; + std::vector<Transition*> _transitions; + std::list<xercesc::DOMElement*> _globalScripts; + + boost::dynamic_bitset<> _configuration; + boost::dynamic_bitset<> _invocations; + boost::dynamic_bitset<> _history; + boost::dynamic_bitset<> _initializedData; + + std::set<boost::dynamic_bitset<> > _microstepConfigurations; + + Binding _binding; + xercesc::DOMElement* _scxml; + X _xmlPrefix; + X _xmlNS; + + bool _isInitialized; + bool _isCancelled; + Event _event; // we do not care about the event's representation + +private: + std::list<xercesc::DOMElement*> getHistoryCompletion(const xercesc::DOMElement* state); + void resortStates(xercesc::DOMNode* node, const X& xmlPrefix); + +// bool hasLegalConfiguration(); + +#ifdef USCXML_VERBOSE + void printStateNames(const boost::dynamic_bitset<>& bitset); +#endif + +}; + +} + +#endif /* end of include guard: INTERPRETERFAST_H_DA55E52B */ + diff --git a/src/uscxml/interpreter/MicroStepImpl.h b/src/uscxml/interpreter/MicroStepImpl.h new file mode 100644 index 0000000..71c03b5 --- /dev/null +++ b/src/uscxml/interpreter/MicroStepImpl.h @@ -0,0 +1,127 @@ +/** + * @file + * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#ifndef MICROSTEPIMPL_H_98233709 +#define MICROSTEPIMPL_H_98233709 + +#include <memory> +#include <mutex> +#include <list> +#include <map> +#include <string> + +#include "uscxml/Common.h" +#include "uscxml/messages/Event.h" +#include "uscxml/interpreter/InterpreterMonitor.h" +#include "uscxml/util/DOM.h" +#include <xercesc/dom/DOM.hpp> + +namespace uscxml { + +enum InterpreterState { + + + USCXML_FINISHED = -2, ///< machine reached a final configuration and is done + USCXML_INTERRUPTED = -1, ///< machine received the empty event on the external queue + USCXML_UNDEF = 0, ///< not an actual state + USCXML_IDLE = 1, ///< stable configuration and queues empty + USCXML_INITIALIZED = 2, ///< DOM is setup and all external components instantiated + USCXML_INSTANTIATED = 3, ///< nothing really, just instantiated + USCXML_MICROSTEPPED = 4, ///< processed one transition set + USCXML_MACROSTEPPED = 5, ///< processed all transition sets and reached a stable configuration + USCXML_CANCELLED = 6, ///< machine was cancelled, step once more to finalize +}; + +class USCXML_API MicroStepCallbacks { +public: + /** Event Queues / Matching */ + virtual Event dequeueInternal() = 0; + virtual Event dequeueExternal(bool blocking) = 0; + virtual bool isMatched(const Event& event, const std::string& eventDesc) = 0; + virtual void raiseDoneEvent(xercesc::DOMElement* state, xercesc::DOMElement* doneData) = 0; + + /** Datamodel */ + virtual bool isTrue(const std::string& expr) = 0; + virtual void initData(xercesc::DOMElement* element) = 0; + + /** Executable Content */ + virtual void process(xercesc::DOMElement* block) = 0; + + /** Invocations */ + virtual void invoke(xercesc::DOMElement* invoke) = 0; + virtual void uninvoke(xercesc::DOMElement* invoke) = 0; + + /** Monitoring */ + virtual InterpreterMonitor* getMonitor() = 0; +}; + +class USCXML_API MicroStepImpl { +public: + enum Binding { + EARLY = 0, + LATE = 1 + }; + + MicroStepImpl(MicroStepCallbacks* callbacks) : _callbacks(callbacks) {} + + virtual InterpreterState step(bool blocking) = 0; + virtual void reset() = 0; ///< Reset state machine + virtual bool isInState(const std::string& stateId) = 0; + virtual std::list<xercesc::DOMElement*> getConfiguration() = 0; + + virtual void init(xercesc::DOMElement* scxml) = 0; + virtual void markAsCancelled() = 0; + +protected: + MicroStepCallbacks* _callbacks; + +}; + +class USCXML_API MicroStep { +public: + PIMPL_OPERATORS(MicroStep) + + virtual InterpreterState step(bool blocking) { + return _impl->step(blocking); + } + virtual void reset() { + return _impl->reset(); + } + virtual bool isInState(const std::string& stateId) { + return _impl->isInState(stateId); + } + + std::list<xercesc::DOMElement*> getConfiguration() { + return _impl->getConfiguration(); + } + + virtual void init(xercesc::DOMElement* scxml) { + _impl->init(scxml); + } + + virtual void markAsCancelled() { + _impl->markAsCancelled(); + } +protected: + std::shared_ptr<MicroStepImpl> _impl; +}; + +} + +#endif /* end of include guard: MICROSTEPIMPL_H_98233709 */ |