/** * @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 . * @endcond */ #include "uscxml/transform/ChartToFSM.h" #include "uscxml/transform/FlatStateIdentifier.h" #include "uscxml/Convenience.h" #include "uscxml/Factory.h" #include "uscxml/debug/Complexity.h" #include #include #include #include #include "uscxml/UUID.h" #include #include #include #undef max #include #define UNDECIDABLE 2147483647 #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) #define DUMP_STATS(nrTrans, disregardTime) \ uint64_t now = tthread::chrono::system_clock::now(); \ if (now - _lastTimeStamp > 1000 || disregardTime) { \ std::cerr << "## Transition: " << _perfTransUsed << " / " << _perfTransTotal << " [" << _perfTransProcessed << "/sec]"; \ if (nrTrans > 0) { \ std::cerr << " - 2**" << nrTrans << " = " << pow(2.0, static_cast(nrTrans)); \ } \ std::cerr << std::endl; \ std::cerr << "## State : " << _globalConf.size() << " found / " << _perfStackSize << " stacked / " << _perfStatesTotal << " seen [" << _perfStatesProcessed << "/sec]" << std::endl; \ std::cerr << "## Microstep : " << _perfMicroStepTotal << " [" << _perfMicroStepProcessed << "/sec]" << std::endl; \ std::cerr << "## Cached : " << _perfStatesCachedTotal << " [" << _perfStatesCachedProcessed << "/sec]" << std::endl; \ std::cerr << "## Skipped : " << _perfStatesSkippedTotal << " [" << _perfStatesSkippedProcessed << "/sec]" << std::endl; \ std::cerr << "## Queues : " << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << " / " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; \ std::cerr << "toFSM: "; \ std::cerr << _perfTransUsed << ", " << _perfTransTotal << ", " << _perfTransProcessed << ", "; \ std::cerr << _globalConf.size() << ", " << _perfStackSize << ", " << _perfStatesTotal << ", " << _perfStatesProcessed << ", "; \ std::cerr << _perfMicroStepTotal << ", " << _perfMicroStepProcessed << ", "; \ std::cerr << _perfStatesCachedTotal << ", " << _perfStatesCachedProcessed << ", " << _perfStatesSkippedTotal << ", " << _perfStatesSkippedProcessed << ", "; \ std::cerr << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << ", " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; \ std::cerr << std::endl; \ _perfTransProcessed = 0; \ _perfStatesProcessed = 0; \ _perfStatesCachedProcessed = 0; \ _perfStatesSkippedProcessed = 0; \ _perfMicroStepProcessed = 0; \ if (!disregardTime)\ _lastTimeStamp = now; \ } //std::cerr << "Q: " << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << " / " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; #define DUMP_TRANSSET(where) \ {\ std::cout << std::endl;\ std::cout << "** " << transitions.size() << " ** " << where << std::endl;\ for (size_t m = 0; m < transitions.size(); m++) {\ std::cout << transitions[m] << std::endl;\ }\ } namespace uscxml { using namespace Arabica::DOM; using namespace Arabica::XPath; #define DETAIL_EXEC_CONTENT(field, actPtr) \ std::cerr << " " << #field << " / " << TAGNAME_CAST(actPtr->field) << " ("; \ NodeSet contents = filterChildType(Node_base::ELEMENT_NODE, actPtr->field, true); \ for (size_t i = 0; i < contents.size(); i++) { \ std::cerr << " " << TAGNAME_CAST(contents[i]); \ } \ std::cerr << ")"; ChartToFSM::ChartToFSM(const Interpreter& other) { cloneFrom(other.getImpl()); _transitionsFromTree = true; _keepInvalidTransitions = false; _lastTimeStamp = tthread::chrono::system_clock::now(); _perfTransProcessed = 0; _perfTransTotal = 0; _perfTransUsed = 0; _perfStatesTotal = 0; _perfStatesProcessed = 0; _perfStackSize = 0; _perfStatesSkippedProcessed = 0; _perfStatesSkippedTotal = 0; _perfStatesCachedProcessed = 0; _perfStatesCachedTotal = 0; _perfMicroStepProcessed = 0; _perfMicroStepTotal = 0; if (envVarIEquals("USCXML_TRANSFORM_TRANS_FROM", "powerset")) _transitionsFromTree = false; _start = NULL; _currGlobalTransition = NULL; _transTree = NULL; _lastStateIndex = 0; _lastActiveIndex = 0; _lastTransIndex = 0; _maxEventSentChain = 0; _maxEventRaisedChain = 0; _doneEventRaiseTolerance = 0; _skipEventChainCalculations = false; addMonitor(this); } ChartToFSM::~ChartToFSM() { std::map::iterator confIter = _globalConf.begin(); while(confIter != _globalConf.end()) { std::list::iterator transIter = confIter->second->sortedOutgoing.begin(); while (transIter != confIter->second->sortedOutgoing.end()) { delete *transIter; transIter++; } delete confIter->second; confIter++; } // tear down caches Arabica::XPath::NodeSet allTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); for (size_t i = 0; i < allTransitions.size(); i++) { _transParents.erase(allTransitions[i]); } } Document ChartToFSM::getDocument() const { if (_flatDoc) return _flatDoc; return _document; } InterpreterState ChartToFSM::interpret() { init(); setupIOProcessors(); { std::list > > allConfig = Complexity::getAllConfigurations(_scxml); for (std::list > >::iterator confIter = allConfig.begin(); confIter != allConfig.end(); confIter++) { std::string seperator; NodeSet configNodeSet; std::set > config = *confIter; for (std::set >::iterator elemIter = config.begin(); elemIter != config.end(); elemIter++) { // std::cerr << seperator << ATTR((*elemIter), "id"); seperator = ","; configNodeSet.push_back(*elemIter); } assert(isLegalConfiguration(configNodeSet)); // std::cerr << std::endl; } } std::map histoGramm = Complexity::getTransitionHistogramm(_scxml); // abort(); uint64_t complexity = Complexity::stateMachineComplexity(this) + 1; std::cerr << "Approximate Complexity: " << complexity << std::endl; std::cerr << "Approximate Active Complexity: " << Complexity::stateMachineComplexity(this, Complexity::IGNORE_HISTORY | Complexity::IGNORE_NESTED_DATA) + 1 << std::endl; if (complexity > 1000) { _skipEventChainCalculations = true; _maxEventRaisedChain = UNDECIDABLE; _maxEventSentChain = UNDECIDABLE; } // initialize the datamodel std::string datamodelName; if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "datamodel")) datamodelName = ATTR(_scxml, "datamodel"); if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "profile")) // SCION SCXML uses profile to specify datamodel datamodelName = ATTR(_scxml, "profile"); if(datamodelName.length() > 0) { _dataModel = _factory->createDataModel(datamodelName, this); if (!_dataModel) { Event e; e.data.compound["cause"] = Data("Cannot instantiate datamodel", Data::VERBATIM); throw e; } } else { _dataModel = _factory->createDataModel("null", this); } if(datamodelName.length() > 0 && !_dataModel) { LOG(ERROR) << "No datamodel for " << datamodelName << " registered"; } // setup caches { Arabica::XPath::NodeSet allTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); indexedTransitions.reserve(allTransitions.size()); for (size_t i = 0; i < allTransitions.size(); i++) { _transParents[allTransitions[i]] = InterpreterImpl::getParentState(allTransitions[i]); } } // identify all history elements NodeSet histories = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "history", _scxml, true); for (size_t i = 0; i < histories.size(); i++) { _historyTargets[ATTR_CAST(histories[i], "id")] = Element(histories[i]); } _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); _alreadyFlat = (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))); if (_alreadyFlat) { reassembleFromFlat(); return _state; } // set invokeid for all invokers to parent state if none given NodeSet invokers = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true); for (size_t i = 0; i < invokers.size(); i++) { Element invokerElem = Element(invokers[i]); invokerElem.setAttribute("parent", ATTR_CAST(invokerElem.getParentNode(), "id")); } // reset _globalConf.clear(); _currGlobalTransition = NULL; // very first state _start = new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this); _globalConf[_start->stateId] = _start; _globalConf[_start->stateId]->index = _lastStateIndex++; NodeSet initialTransitions; // enter initial configuration Arabica::XPath::NodeSet initialStates; initialStates = getInitialStates(); assert(initialStates.size() > 0); for (size_t i = 0; i < initialStates.size(); i++) { Element initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); _nsInfo.setPrefix(initialElem); initialElem.setAttribute("generated", "true"); Element transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); _nsInfo.setPrefix(transitionElem); transitionElem.setAttribute("target", ATTR_CAST(initialStates[i], "id")); initialElem.appendChild(transitionElem); _scxml.appendChild(initialElem); initialTransitions.push_back(transitionElem); } if (!_skipEventChainCalculations) annotateRaiseAndSend(_scxml); // std::cout << _scxml << std::endl; indexTransitions(); // add initial transitions as least prior for (size_t i = 0; i < initialTransitions.size() ; i++) { indexedTransitions.push_back(Element(initialTransitions[i])); } // set index attribute for transitions for (size_t i = 0; i < indexedTransitions.size(); i++) { // std::cerr << toStr(i) << ":" << (HAS_ATTR(indexedTransitions[i], "line_start") ? ATTR(indexedTransitions[i], "line_start") : ""); // std::cerr << "\t" << DOMUtils::xPathForNode(indexedTransitions[i]) << std::endl; indexedTransitions[i].setAttribute("index", toStr(i)); } // int lastTransIndex = indexedTransitions.size(); // for (size_t i = 0; i < initialTransitions.size() ; i++, lastTransIndex++) { // indexedTransitions[i].setAttribute("index", toStr(indexedTransitions.size() - 1 - i)); // } // gather and set index attribute o states NodeSet allStates = getAllStates(); allStates.to_document_order(); indexedStates.resize(allStates.size()); for (size_t i = 0; i < allStates.size(); i++) { Element state = Element(allStates[i]); // while we are iterating, determine deepest nested level size_t nrAncs = getProperAncestors(state, _scxml).size(); if (_doneEventRaiseTolerance < nrAncs) _doneEventRaiseTolerance = nrAncs; state.setAttribute("index", toStr(i)); indexedStates[i] = state; } // std::cerr << _scxml << std::endl; // create a _flatDoc for the FSM DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); _flatDoc = domFactory.createDocument(_document.getNamespaceURI(), "", 0); GlobalTransition* globalTransition = new GlobalTransition(initialTransitions, _dataModel, this); globalTransition->index = _lastTransIndex++; _start->sortedOutgoing.push_back(globalTransition); globalTransition->source = _start->stateId; _currGlobalTransition = globalTransition; enterStates(initialTransitions); globalTransition->destination = FlatStateIdentifier::toStateId(_configuration); globalTransition->activeDestination = globalTransition->destination; explode(); DUMP_STATS(0, true); #if 0 // print set of global configurations for(std::map::iterator globalConfIter = _globalConf.begin(); globalConfIter != _globalConf.end(); globalConfIter++) { std::cerr << globalConfIter->first << std::endl; } std::cerr << _globalConf.size() << std::endl; #endif std::cerr << "Actual Complexity: " << _globalConf.size() << std::endl; std::cerr << "Actual Active Complexity: " << _activeConf.size() << std::endl; std::cerr << "Internal Queue: " << _maxEventRaisedChain << std::endl; std::cerr << "External Queue: " << _maxEventSentChain << std::endl; // if (complexity < _globalConf.size()) // throw std::runtime_error("Upper bound for states exceeded"); return _state; } void ChartToFSM::executeContent(const Arabica::DOM::Element& content, bool rethrow) { // std::cerr << content << std::endl; // std::cerr << TAGNAME(content) << std::endl; GlobalTransition::Action action; NodeList childs = content.getChildNodes(); for (unsigned int i = 0; i < childs.getLength(); i++) { Node_base::Type type = childs.item(i).getNodeType(); if (type == Node_base::ELEMENT_NODE || type == Node_base::COMMENT_NODE || type == Node_base::TEXT_NODE) { goto HAS_VALID_CHILDREN; } } return; HAS_VALID_CHILDREN: if (false) { } else if (TAGNAME(content) == "transition") { action.transition = content; } else if (TAGNAME(content) == "onexit") { action.onExit = content; } else if (TAGNAME(content) == "onentry") { action.onEntry = content; } else if (TAGNAME(content) == "history") { assert(false); } else { // e.g. global script elements return; } if (!_skipEventChainCalculations && (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) { assert(content.hasAttribute("raise") && content.hasAttribute("send")); std::string raiseAttr = content.getAttribute("raise"); std::string sendAttr = content.getAttribute("send"); _currGlobalTransition->eventsRaised = (raiseAttr == "-1" ? UNDECIDABLE : _currGlobalTransition->eventsRaised + strTo(raiseAttr)); _currGlobalTransition->eventsSent = (sendAttr == "-1" ? UNDECIDABLE : _currGlobalTransition->eventsSent + strTo(sendAttr)); if (_currGlobalTransition->eventsRaised > _maxEventRaisedChain) _maxEventRaisedChain = _currGlobalTransition->eventsRaised; if (_currGlobalTransition->eventsSent > _maxEventSentChain) _maxEventSentChain = _currGlobalTransition->eventsSent; } _currGlobalTransition->actions.push_back(action); _currGlobalTransition->hasExecutableContent = true; } void ChartToFSM::invoke(const Arabica::DOM::Element& element) { GlobalTransition::Action action; action.invoke = element; _currGlobalTransition->actions.push_back(action); _currGlobalTransition->hasExecutableContent = true; } void ChartToFSM::cancelInvoke(const Arabica::DOM::Element& element) { GlobalTransition::Action action; action.uninvoke = element; _currGlobalTransition->actions.push_back(action); _currGlobalTransition->hasExecutableContent = true; } void ChartToFSM::internalDoneSend(const Arabica::DOM::Element& state, const Arabica::DOM::Element& doneData) { if (!isState(state)) return; // if (LOCALNAME(state) == "scxml") // return; // if (parentIsScxmlState(state)) // return; // return; // std::cerr << "internalDoneSend: " << state << std::endl; // create onentry with a raise element Element onentry = _flatDoc.createElementNS(_nsInfo.nsURL, "onentry"); _nsInfo.setPrefix(onentry); Element raise = _flatDoc.createElementNS(_nsInfo.nsURL, "raise"); _nsInfo.setPrefix(raise); onentry.appendChild(raise); if (doneData) { Arabica::XPath::NodeSet contents = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "content", doneData); if (contents.size() > 0) { Node imported = _flatDoc.importNode(contents[0], true); raise.appendChild(imported); } Arabica::XPath::NodeSet params = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "param", doneData); if (params.size() > 0) { Node imported = _flatDoc.importNode(params[0], true); raise.appendChild(imported); } } raise.setAttribute("event", "done.state." + ATTR_CAST(state, "id")); // parent?! GlobalTransition::Action action; action.raiseDone = onentry; // HERE! _currGlobalTransition->actions.push_back(action); if (!_skipEventChainCalculations && (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) _currGlobalTransition->eventsRaised++; _currGlobalTransition->hasExecutableContent = true; } static bool isSuperset(const GlobalTransition* t1, const GlobalTransition* t2) { bool isSuperset = true; if (t1->transitionRefs.size() >= t2->transitionRefs.size()) return false; NodeSet t1Trans = t1->getTransitions(); NodeSet t2Trans = t2->getTransitions(); for (size_t i = 0; i < t1Trans.size(); i++) { if (!InterpreterImpl::isMember(t1Trans[i], t2Trans)) { isSuperset = false; } } return isSuperset; } // return false if two transitions have the same source std::map, Arabica::DOM::Node > ChartToFSM::_transParents; bool ChartToFSM::filterSameState(const NodeSet& transitions) { for (unsigned int i = 0; i < transitions.size(); i++) { Node p1 = _transParents[transitions[i]]; for (unsigned int j = i + 1; j < transitions.size(); j++) { // if (i == j) // continue; Node p2 = _transParents[transitions[j]]; if (p1 == p2) return false; } } return true; } static bool filterSameHierarchy(const NodeSet& transitions) { for (unsigned int i = 0; i < transitions.size(); i++) { Node t1 = transitions[i]; Node p1 = InterpreterImpl::getParentState(t1); for (unsigned int j = i + 1; j < transitions.size(); j++) { Node t2 = transitions[j]; Node p2 = InterpreterImpl::getParentState(t2); while(p2) { if (p1 == p2) { return false; } p2 = p2.getParentNode(); } } } return true; } static bool filterChildEnabled(const NodeSet& transitions) { // drop any transition that is already enabled by a child NodeSet filteredTransitions; for (unsigned int i = 0; i < transitions.size(); i++) { Node t1 = transitions[i]; Node p1 = InterpreterImpl::getParentState(t1); for (unsigned int j = 0; j < transitions.size(); j++) { if (i == j) continue; Node t2 = transitions[j]; Node p2 = InterpreterImpl::getParentState(t2); p2 = p2.getParentNode(); // TODO: think about again! while(p2) { if (p1 == p2) { std::string eventDesc1 = ATTR_CAST(t1, "event"); std::string eventDesc2 = ATTR_CAST(t2, "event"); if (nameMatch(eventDesc1, eventDesc2)) { return false; } } p2 = p2.getParentNode(); } } filteredTransitions.push_back(t1); ; } return true; } bool ChartToFSM::hasForeachInBetween(const Arabica::DOM::Node& ancestor, const Arabica::DOM::Node& child) { if (!ancestor || !child) return false; Node currChild = child; while(currChild != ancestor) { if (!currChild.getParentNode()) return false; if (TAGNAME_CAST(currChild) == "foreach") return true; currChild = currChild.getParentNode(); } return false; } void ChartToFSM::annotateRaiseAndSend(const Arabica::DOM::Element& root) { NodeSet execContent; execContent.push_back(DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true)); execContent.push_back(DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "onentry", _scxml, true)); execContent.push_back(DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "onexit", _scxml, true)); for (size_t i = 0; i < execContent.size(); i++) { Element execContentElem(execContent[i]); int nrRaise = 0; NodeSet raise = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "raise", execContent[i], true); for (size_t j = 0; j < raise.size(); j++) { if (hasForeachInBetween(execContent[i], raise[j])) { execContentElem.setAttribute("raise", "-1"); goto DONE_COUNT_RAISE; } else { nrRaise++; } } execContentElem.setAttribute("raise", toStr(nrRaise)); DONE_COUNT_RAISE: int nrSend = 0; NodeSet sends = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "send", execContent[i], true); for (size_t j = 0; j < sends.size(); j++) { if (hasForeachInBetween(execContent[i], sends[j])) { execContentElem.setAttribute("send", "-1"); goto DONE_COUNT_SEND; } else { nrSend++; } } execContentElem.setAttribute("send", toStr(nrSend)); DONE_COUNT_SEND: ; } } void ChartToFSM::annotateDomain() { Arabica::XPath::NodeSet allTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); for (size_t i = 0; i < allTransitions.size(); i++) { Element transition(allTransitions[i]); Arabica::DOM::Node domain = getTransitionDomain(transition); if (domain) { transition.setAttribute("domain", (HAS_ATTR_CAST(domain, "id") ? ATTR_CAST(domain, "id") : DOMUtils::xPathForNode(domain))); } else { transition.setAttribute("domain", "#UNDEF"); } } } void ChartToFSM::annotateExitSet() { Arabica::XPath::NodeSet allTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); for (size_t i = 0; i < allTransitions.size(); i++) { Element transition(allTransitions[i]); Arabica::DOM::Node domain = getTransitionDomain(transition); Arabica::XPath::NodeSet allStates = getAllStates(); std::ostringstream exitSetStr; std::string seperator = ""; for (size_t j = 0; j < allStates.size(); j++) { Element state(allStates[j]); if (state.getParentNode() == domain) { exitSetStr << seperator << (HAS_ATTR(state, "id") ? ATTR(state, "id") : DOMUtils::xPathForNode(state)); seperator = ", "; } } transition.setAttribute("exitset", exitSetStr.str()); } } void ChartToFSM::annotateEntrySet() { Arabica::XPath::NodeSet allTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); for (size_t i = 0; i < allTransitions.size(); i++) { Element transition(allTransitions[i]); NodeSet tmpTransitions; NodeSet tmpStatesToEnter; NodeSet tmpStatesForDefaultEntry; std::map > tmpDefaultHistoryContent; tmpTransitions.push_back(transition); computeEntrySet(tmpTransitions, tmpStatesToEnter, tmpStatesForDefaultEntry, tmpDefaultHistoryContent); std::ostringstream entrySetStr; std::string seperator = ""; for (size_t j = 0; j < tmpStatesToEnter.size(); j++) { Element state(tmpStatesToEnter[j]); entrySetStr << seperator << (HAS_ATTR(state, "id") ? ATTR(state, "id") : DOMUtils::xPathForNode(state)); seperator = ", "; } for (size_t j = 0; j < tmpStatesForDefaultEntry.size(); j++) { Element state(tmpStatesForDefaultEntry[j]); entrySetStr << seperator << (HAS_ATTR(state, "id") ? ATTR(state, "id") : DOMUtils::xPathForNode(state)); seperator = ", "; } transition.setAttribute("entryset", entrySetStr.str()); } } void ChartToFSM::annotateConflicts() { Arabica::XPath::NodeSet allTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); Arabica::XPath::NodeSet allStates = getAllStates(); for (size_t i = 0; i < allTransitions.size(); i++) { Element t1(allTransitions[i]); if (!isState(Element(t1.getParentNode()))) continue; Arabica::DOM::Node d1 = getTransitionDomain(t1); Arabica::XPath::NodeSet exitSet1; for (size_t k = 0; k < allStates.size(); k++) { Element state(allStates[k]); if (isDescendant(state, d1)) { exitSet1.push_back(state); } } std::ostringstream preemptionStr; std::string seperator = ""; for (size_t j = 0; j < allTransitions.size(); j++) { if ( i == j) continue; Element t2(allTransitions[j]); if (!isState(Element(t2.getParentNode()))) continue; Arabica::DOM::Node d2 = getTransitionDomain(t2); Arabica::XPath::NodeSet exitSet2; for (size_t k = 0; k < allStates.size(); k++) { Element state(allStates[k]); if (isDescendant(state, d2)) { exitSet2.push_back(state); } } if (hasIntersection(exitSet1, exitSet2)) { preemptionStr << seperator << (HAS_ATTR(t2, "priority") ? ATTR(t2, "priority") : DOMUtils::xPathForNode(t2)); seperator = ", "; } // if (isDescendant(d1, d2) || isDescendant(d2, d1) || d1 == d2) { // preemptionStr << seperator << ATTR(t2, "priority"); // seperator = ", "; // } } if (preemptionStr.str().size() > 0) t1.setAttribute("conflicts", preemptionStr.str()); } } void ChartToFSM::indexTransitions() { indexedTransitions.clear(); indexTransitions(_scxml); #if 1 size_t index = indexedTransitions.size() - 1; for (std::vector >::iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { transIter->setAttribute("priority", toStr(index)); index--; } #else size_t index = 0; for (std::vector >::iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { transIter->setAttribute("priority", toStr(index)); index++; } #endif // reverse indices for most prior to be in front //std::reverse(indexedTransitions.begin(), indexedTransitions.end()); } #if 0 void ChartToFSM::indexTransitions(const Arabica::DOM::Element& root) { // breadth first traversal of transitions Arabica::XPath::NodeSet levelTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", root); for (int i = levelTransitions.size() - 1; i >= 0; i--) { // push into index starting with least prior indexedTransitions.push_back(Element(levelTransitions[i])); } Arabica::XPath::NodeSet nextLevel = filterChildType(Arabica::DOM::Node_base::ELEMENT_NODE, root); for (int i = nextLevel.size() - 1; i >= 0; i--) { Element stateElem = Element(nextLevel[i]); if (isState(stateElem)) indexTransitions(stateElem); } } #else void ChartToFSM::indexTransitions(const Arabica::DOM::Element& root) { // Post-order traversal of transitions Arabica::XPath::NodeSet childStates = getChildStates(root); for (size_t i = 0; i < childStates.size(); i++) { Element childElem(childStates[i]); indexTransitions(childElem); } Arabica::XPath::NodeSet levelTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", root); for (size_t i = 0; i < levelTransitions.size(); i++) { // push into index starting with least prior indexedTransitions.push_back(Element(levelTransitions[i])); } } #endif bool GlobalTransition::operator< (const GlobalTransition& other) const { const std::vector >& indexedTransitions = interpreter->indexedTransitions; NodeSet transitions = getTransitions(); for (std::vector >::const_iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { const Element& refTrans = *transIter; NodeSet otherTransitions = other.getTransitions(); if (InterpreterImpl::isMember(refTrans, transitions) && !InterpreterImpl::isMember(refTrans, otherTransitions)) { return true; } if (!InterpreterImpl::isMember(refTrans, transitions) && InterpreterImpl::isMember(refTrans, otherTransitions)) { return false; } } return true; // actually, they are equal } template bool PtrComp(const T * const & a, const T * const & b) { return *a < *b; } /** * subset only removes transitions without cond -> superset will always be enabled */ bool hasUnconditionalSuperset(GlobalTransition* first, GlobalTransition* second) { NodeSet firstTransitions = first->getTransitions(); NodeSet secondTransitions = second->getTransitions(); // if (first->condition.size() > 0) // return false; if (isSuperset(second, first)) { for (size_t i = 0; i < firstTransitions.size(); i++) { if (!InterpreterImpl::isMember(firstTransitions[i], secondTransitions)) { if (HAS_ATTR_CAST(firstTransitions[i], "cond")) { return false; // second can't be removed } } } return true; // remove second } return false; //second can't be removed } /** * earlier transition is conditionless for same event */ bool hasEarlierUnconditionalMatch(GlobalTransition* first, GlobalTransition* second) { if (first->eventDesc == second->eventDesc) { if (first->condition.size() == 0) return true; } return false; } std::list redundantRemove(std::list list) { #if 1 std::list::iterator outerIter; std::list::iterator innerIter; outerIter = list.begin(); while(outerIter != list.end()) { innerIter = outerIter; while(innerIter != list.end()) { if (innerIter == outerIter) { innerIter++; continue; } GlobalTransition* t1 = *outerIter; GlobalTransition* t2 = *innerIter; if (hasUnconditionalSuperset(t1, t2)) { list.erase(innerIter++); continue; } else if (hasUnconditionalSuperset(t2, t1)) { list.erase(outerIter++); break; } if (hasEarlierUnconditionalMatch(t1, t2)) { list.erase(innerIter++); continue; } innerIter++; } outerIter++; } #else for (std::list::iterator outerIter = list.begin(); outerIter != list.end(); outerIter++) { for (std::list::iterator innerIter = outerIter; innerIter != list.end(); innerIter++) { if (innerIter == outerIter) continue; GlobalTransition* t1 = *outerIter; GlobalTransition* t2 = *innerIter; if (hasUnconditionalSuperset(t1, t2)) { innerIter = list.erase(innerIter); continue; } else if (hasUnconditionalSuperset(t2, t1)) { outerIter = list.erase(outerIter); continue; } if (hasEarlierUnconditionalMatch(t1, t2)) { innerIter = list.erase(innerIter); continue; } } } #endif return list; } std::list redundantMark(std::list list) { #if 1 std::list::iterator outerIter; std::list::iterator innerIter; outerIter = list.begin(); while(outerIter != list.end()) { innerIter = outerIter; while(innerIter != list.end()) { if (innerIter == outerIter) { innerIter++; continue; } GlobalTransition* t1 = *outerIter; GlobalTransition* t2 = *innerIter; if (!t1->isValid || !t2->isValid) { innerIter++; continue; } if (hasUnconditionalSuperset(t1, t2)) { t2->isValid = false; t2->invalidMsg = "Unconditional superset"; t2->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; innerIter++; continue; } else if (hasUnconditionalSuperset(t2, t1)) { t1->isValid = false; t1->invalidMsg = "Unconditional superset"; t1->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; outerIter++; break; } if (hasEarlierUnconditionalMatch(t1, t2)) { t2->isValid = false; t2->invalidMsg = "Earlier unconditional match"; t2->invalidReason = GlobalTransition::UNCONDITIONAL_MATCH; innerIter++; continue; } innerIter++; } outerIter++; } #else for (std::list::iterator outerIter = list.begin(); outerIter != list.end(); outerIter++) { for (std::list::iterator innerIter = outerIter; innerIter != list.end(); innerIter++) { if (innerIter == outerIter) continue; GlobalTransition* t1 = *outerIter; GlobalTransition* t2 = *innerIter; if (!t1->isValid || !t2->isValid) continue; if (hasUnconditionalSuperset(t1, t2)) { t2->isValid = false; t2->invalidMsg = "Unconditional superset"; t2->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; continue; } else if (hasUnconditionalSuperset(t2, t1)) { t1->isValid = false; t1->invalidMsg = "Unconditional superset"; t1->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; continue; } if (hasEarlierUnconditionalMatch(t1, t2)) { t2->isValid = false; t2->invalidMsg = "Earlier unconditional match"; t2->invalidReason = GlobalTransition::UNCONDITIONAL_MATCH; continue; } } } #endif return list; } void TransitionTreeNode::dump(int indent) { std::string padding; for (size_t i = 0; i + 1 < indent; i++) { padding += "| "; } if (indent > 0) padding += "|-"; std::string typeString; switch (type) { case TYPE_NESTED: typeString = "NESTED"; break; case TYPE_PARALLEL: typeString = "PARALLEL"; break; case TYPE_TRANSITION: typeString = "TRANSITION"; break; case TYPE_UNDEFINED: typeString = "UNDEFINED"; break; break; default: break; } if (transition) { std::cerr << padding << "t" << ATTR(transition, "index") << " " << typeString << ": "; // std::cerr << (prevTransition != NULL ? " (" + prevTransition->nodeId + ") <-" : ""); std::cerr << "[" << nodeId << "]"; // std::cerr << (nextTransition != NULL ? " -> (" + nextTransition->nodeId + ")" : ""); std::cerr << std::endl; } else { std::cerr << padding << ATTR(state, "id") << " " << typeString << ": " << "[" << nodeId << "]"; // std::cerr << (firstTransition != NULL ? " -> " + firstTransition->nodeId : ""); std::cerr << std::endl; } for (std::list::iterator childIter = children.begin(); childIter != children.end(); childIter++) { (*childIter)->dump(indent + 1); } } void ChartToFSM::getPotentialTransitionsForConfFromTree(const Arabica::XPath::NodeSet& conf, std::map& outMap) { if (_transTree == NULL) { _transTree = buildTransTree(_scxml, "0"); // _transTree->dump(); } std::string seperator; // std::cerr << "--- "; // recursion start std::set transLeafs; for (size_t i = 0; i < conf.size(); i++) { DUMP_STATS(conf.size(), false); Element confElem(conf[i]); assert(_stateToTransTreeNode.find(confElem) != _stateToTransTreeNode.end()); TransitionTreeNode* node = _stateToTransTreeNode[confElem]; if (node->firstState == NULL) { // a leaf - ignore intermediates // ascend to the first parent with transitions but stop at parallel nodes while(node != NULL && node->firstTransition == NULL) { if (node->parent && node->parent->type == TransitionTreeNode::TYPE_PARALLEL) break; node = node->parent; } if (node != NULL) { transLeafs.insert(node); } else { //std::cerr << ATTR(confElem, "id") << " does not cause transitions" << std::endl; } } } std::list > stack; stack.push_back(transLeafs); // push follow-up configurations onto stack while (stack.size() > 0) { // pop from front of stack std::set stateList = stack.front(); stack.pop_front(); DUMP_STATS(conf.size(), false); #if 0 seperator = ""; std::cerr << "Current set: "; for (std::set::iterator transIter = stateList.begin(); transIter != stateList.end(); transIter++) { std::cerr << seperator << (*transIter)->nodeId; seperator = ", "; } std::cerr << std::endl; #endif /* * TransNodes contains a set of lists of transitions. * In the inner stack we build every possible combination * of picking at-most one from each list. */ /* create global transitions for every n-tuple in current set of lists */ std::list, std::set > > innerStack; innerStack.push_back(std::make_pair(std::set(), stateList)); while(innerStack.size() > 0) { // picking at-most one from each list std::set remainingStates = innerStack.front().second; std::set fixedTransitions = innerStack.front().first; innerStack.pop_front(); if (remainingStates.size() > 0) { // iterate for each first element fixed TransitionTreeNode* firstRemainingState = *remainingStates.begin(); remainingStates.erase(remainingStates.begin()); if (firstRemainingState->firstTransition == NULL) { // no transitions at this state - reenqueue with NULL selection from this innerStack.push_back(std::make_pair(fixedTransitions, remainingStates)); continue; } TransitionTreeNode* currTrans = firstRemainingState->firstTransition; // choose none from firstList innerStack.push_back(std::make_pair(fixedTransitions, remainingStates)); while(currTrans != NULL) { std::set fixedAndThis(fixedTransitions); fixedAndThis.insert(currTrans); innerStack.push_back(std::make_pair(fixedAndThis, remainingStates)); currTrans = currTrans->nextTransition; } } else { DUMP_STATS(conf.size(), false); if (fixedTransitions.size() > 0) { _perfTransTotal++; _perfTransProcessed++; NodeSet fixed; #if 0 seperator = ""; for (std::set::iterator itemIter = fixedTransitions.begin(); itemIter != fixedTransitions.end(); itemIter++) { TransitionTreeNode* currItem = *itemIter; std::cerr << seperator << currItem->nodeId; seperator = ", "; } std::cerr << " ## "; #endif seperator = ""; for (std::set::iterator itemIter = fixedTransitions.begin(); itemIter != fixedTransitions.end(); itemIter++) { TransitionTreeNode* currItem = *itemIter; fixed.push_back(currItem->transition); // std::cerr << seperator << ATTR(currItem->transition, "index"); seperator = ", "; } // std::cerr << std::endl; // fixed contains a transiton set! assert(filterSameState(fixed)); // assert(filterChildEnabled(fixed)); assert(filterSameHierarchy(fixed)); // do not add if they preempt if (fixed.size() != removeConflictingTransitions(fixed).size()) { // std::cerr << " - PREEMPTS" << std::endl; continue; } GlobalTransition* transition = new GlobalTransition(fixed, _dataModel, this); transition->index = _lastTransIndex++; // assert(outMap.find(transition->transitionId) == outMap.end()); if (!transition->isValid && !_keepInvalidTransitions) { delete(transition); // std::cerr << " - INVALID" << std::endl; continue; } _perfTransUsed++; outMap[transition->transitionId] = transition; // std::cerr << " - GOOD" << std::endl; } } } // create new set of transition lists by moving to parent states for (std::set::iterator stateIter = stateList.begin(); stateIter != stateList.end(); stateIter++) { TransitionTreeNode* origState = *stateIter; TransitionTreeNode* currState = origState; TransitionTreeNode* parentState = currState->parent; /** * We ascend the current state via its parent and add the parent with transitions. * However, we break if we reached the top or if we passed a parallel state for * wich we are not the first child */ while(parentState != NULL) { if (parentState->type == TransitionTreeNode::TYPE_PARALLEL && parentState->firstState != currState) { // the first child of the parallel state will continue this transition - we made sure to keep them break; } if (parentState->firstTransition != NULL) { // std::cerr << "#### Adding new parent lists for " << origState->nodeId << std::endl; std::set newStateList; newStateList.insert(parentState); // add all other states that are not a child of the parent state for (std::set::iterator newlistIter = stateList.begin(); newlistIter != stateList.end(); newlistIter++) { TransitionTreeNode* otherState = *newlistIter; while(otherState != NULL && otherState != parentState) { otherState = otherState->parent; } if (otherState == NULL) newStateList.insert(*newlistIter); } if (newStateList.size() > 0) stack.push_back(newStateList); break; } currState = currState->parent; parentState = currState->parent; } } } } TransitionTreeNode* ChartToFSM::buildTransTree(const Arabica::DOM::Element& root, const std::string& nodeId) { TransitionTreeNode* stateNode = new TransitionTreeNode(); stateNode->nodeId = nodeId; stateNode->state = root; if (TAGNAME(root) == _nsInfo.xmlNSPrefix + "parallel") { stateNode->type = TransitionTreeNode::TYPE_PARALLEL; } else { stateNode->type = TransitionTreeNode::TYPE_NESTED; } // get all transitions and states from root without recursing NodeSet nested; nested.push_back(DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", root)); nested.push_back(DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "state", root)); nested.push_back(DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "final", root)); nested.push_back(DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "parallel", root)); nested.to_document_order(); TransitionTreeNode* lastNode = NULL; for (size_t i = 0; i < nested.size(); i++) { Element nestedElem(nested[i]); if (TAGNAME(nestedElem) == _nsInfo.xmlNSPrefix + "transition") { TransitionTreeNode* transNode = new TransitionTreeNode(); transNode->transition = nestedElem; transNode->parent = stateNode; transNode->nodeId = nodeId + "-" + toStr(i); transNode->type = TransitionTreeNode::TYPE_TRANSITION; if (stateNode->firstTransition == NULL) { stateNode->firstTransition = transNode; } stateNode->children.push_back(transNode); stateNode->lastTransition = transNode; if (lastNode != NULL) { lastNode->nextTransition = transNode; transNode->prevTransition = lastNode; } lastNode = transNode; } else { TransitionTreeNode* deeperNode = buildTransTree(nestedElem, nodeId + "-" + toStr(i)); if (stateNode->firstState == NULL) { stateNode->firstState = deeperNode; } deeperNode->parent = stateNode; stateNode->children.push_back(deeperNode); } } _stateToTransTreeNode[root] = stateNode; return stateNode; } void ChartToFSM::getPotentialTransitionsForConfFromPowerSet(const Arabica::XPath::NodeSet& conf, std::map& outMap) { // get all transition elements from states in the current configuration NodeSet allTransitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", conf); { std::string seperator = ""; for (size_t i = 0; i < allTransitions.size(); i++) { std::cerr << seperator << ATTR_CAST(allTransitions[i], "index"); seperator=", "; } std::cerr << std::endl; } // if (true) { // outMap = _confToTransitions[""]; // } if (allTransitions.size() == 0) return; // no transitions int nrElements = allTransitions.size(); int k = 0; int* stack = (int*)malloc((nrElements + 1) * sizeof(int)); memset(stack, 0, (nrElements + 1) * sizeof(int)); /** * Powerset is too naive and takes too long! * We have it up to 500k checks/sec and still 2**30 is * 1G+ for 30minutes in a single state out of 50k+! */ while(1) { // create the power set of all potential transitions - this is expensive! // see: http://www.programminglogic.com/powerset-algorithm-in-c/ if (stack[k] < nrElements) { stack[k+1] = stack[k] + 1; k++; } else { stack[k-1]++; k--; } if (k==0) break; NodeSet transitions; // std::cerr << globalState->stateId << " [" << nrElements << "]: " << std::endl; for (size_t i = 1; i <= k; i++) { // std::cerr << stack[i] - 1 << ", "; transitions.push_back(allTransitions[stack[i] - 1]); } // std::cerr << std::endl; // transitions.push_back(allTransitions[0]); // transitions.push_back(allTransitions[4]); // transitions.push_back(allTransitions[5]); // transitions.push_back(allTransitions[7]); bool dump = false; // if (k == 4 && stack[1] == 1 && stack[2] == 5 && stack[3] == 6 && stack[4] == 8) { // dump = true; // } if (dump) DUMP_TRANSSET("at start"); _perfTransTotal++; _perfTransProcessed++; DUMP_STATS(nrElements, false); GlobalTransition* transition = NULL; // reduce to conflict-free subset // transitions.to_document_order(); if (!_keepInvalidTransitions) { // remove transitions in the same state if(!filterSameState(transitions)) continue; if (dump) DUMP_TRANSSET("after same state filtered"); // remove those transitions with a child transition // if(!filterChildEnabled(transitions)) if(!filterSameHierarchy(transitions)) continue; if (dump) DUMP_TRANSSET("after child enabled filtered"); transitions = removeConflictingTransitions(transitions); if (dump) DUMP_TRANSSET("after conflicting filtered"); // algorithm can never reduce to empty set assert(transitions.size() > 0); // create a GlobalTransition object from the set transition = new GlobalTransition(transitions, _dataModel, this); if (!transition->isValid) { // this set of transitions can not be enabled together delete transition; continue; } } else { transition = new GlobalTransition(transitions, _dataModel, this); // remove transitions in the same state if(!filterSameState(transitions)) { transition->isValid = false; transition->invalidReason = GlobalTransition::SAME_SOURCE_STATE; transition->invalidMsg = "Same source state"; // } else if(!filterChildEnabled(transitions)) { } else if(!filterSameHierarchy(transitions)) { transition->isValid = false; transition->invalidReason = GlobalTransition::CHILD_ENABLED; transition->invalidMsg = "Nested transitions"; } else { NodeSet nonPreemptingTransitions = removeConflictingTransitions(transitions); if (nonPreemptingTransitions.size() != transitions.size()) { transition->isValid = false; transition->invalidReason = GlobalTransition::PREEMPTING_MEMBERS; transition->invalidMsg = "Preempting members"; } } } // two combinations might have projected onto the same conflict-free set if (outMap.find(transition->transitionId) != outMap.end()) { // std::cerr << "skipping as projected onto existing conflict-free subset" << std::endl; delete transition; continue; } transition->index = _lastTransIndex++; _perfTransUsed++; // remember this conflict-free set // std::cerr << "New conflict-free subset: " << transition->transitionId << ":" << transition->eventDesc << std::endl; outMap[transition->transitionId] = transition; } // _confToTransitions[""] = outMap; return; } void ChartToFSM::explode() { std::list > statesRemaining; statesRemaining.push_back(std::make_pair(_currGlobalTransition, new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this))); // add all invokers for initial transition for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { NodeSet invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); for (unsigned int j = 0; j < invokes.size(); j++) { invoke(Element(invokes[j])); } } _statesToInvoke = NodeSet(); /** We need this to be a recursion in order not to exhaust the stack */ // append new global states and pop from front while(statesRemaining.size() > 0) { _perfStackSize = statesRemaining.size(); _perfStatesTotal++; _perfStatesProcessed++; DUMP_STATS(0, false); GlobalState* globalState = statesRemaining.front().second; _currGlobalTransition = statesRemaining.front().first; statesRemaining.pop_front(); // used to be conditionalized, we will just assume assert(_currGlobalTransition); if (_globalConf.find(globalState->stateId) != _globalConf.end()) { if (_currGlobalTransition->isEventless && !_skipEventChainCalculations && (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) { // we arrived via a spontaneaous transition, do we need to update? updateRaisedAndSendChains(_globalConf[globalState->stateId], _currGlobalTransition, std::set()); } delete globalState; _perfStatesSkippedTotal++; _perfStatesSkippedProcessed++; continue; // we have already been here } _configuration = globalState->getActiveStates(); _alreadyEntered = globalState->getAlreadyEnteredStates(); _historyValue = globalState->getHistoryStates(); // remember as global configuration _globalConf[globalState->stateId] = globalState; _globalConf[globalState->stateId]->index = _lastStateIndex++; if(_globalConf[globalState->stateId]->isFinal) { if (_activeConf.find(globalState->activeId) == _activeConf.end()) { assert(globalState->activeIndex == -1); globalState->activeIndex = _lastActiveIndex++; _activeConf[globalState->activeId] = globalState; // remember as active configuration exitInterpreter(); } continue; // done in this branch } if (_activeConf.find(globalState->activeId) != _activeConf.end()) { // we already know these transition sets, just copy over std::list::iterator sortTransIter = _activeConf[globalState->activeId]->sortedOutgoing.begin(); while(sortTransIter != _activeConf[globalState->activeId]->sortedOutgoing.end()) { globalState->sortedOutgoing.push_back(GlobalTransition::copyWithoutExecContent(*sortTransIter)); globalState->sortedOutgoing.back()->index = _lastTransIndex++; _perfTransUsed++; sortTransIter++; } _perfStatesCachedTotal++; _perfStatesCachedProcessed++; } else { // we need to calculate the potential optimal transition sets std::map transitionSets; // std::cerr << globalState->stateId << std::endl; if (_transitionsFromTree) { getPotentialTransitionsForConfFromTree(refsToStates(globalState->activeStatesRefs), transitionSets); } else { getPotentialTransitionsForConfFromPowerSet(refsToStates(globalState->activeStatesRefs), transitionSets); } // reduce and sort transition sets for(std::map::iterator transSetIter = transitionSets.begin(); transSetIter != transitionSets.end(); transSetIter++) { globalState->sortedOutgoing.push_back(transSetIter->second); } globalState->sortedOutgoing.sort(PtrComp); // globalState->sortedOutgoing.unique(hasUnconditionalSuperset); // globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); // unique is not quite like what we need, but it was a start if (_keepInvalidTransitions) { globalState->sortedOutgoing = redundantMark(globalState->sortedOutgoing); } else { // globalState->sortedOutgoing.unique(hasUnconditionalSuperset); // globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); } // globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); // globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); // // std::cout << globalState->sortedOutgoing.size() << std::endl; assert(_activeConf.find(globalState->activeId) == _activeConf.end()); assert(globalState->activeIndex == -1); globalState->activeIndex = _lastActiveIndex++; _activeConf[globalState->activeId] = globalState; } // take every transition set and append resulting new state for(std::list::iterator transIter = globalState->sortedOutgoing.begin(); transIter != globalState->sortedOutgoing.end(); transIter++) { GlobalTransition* incomingTrans = _currGlobalTransition; GlobalTransition* outgoingTrans = *transIter; outgoingTrans->source = globalState->stateId; if (_keepInvalidTransitions && !outgoingTrans->isValid) continue; _currGlobalTransition = outgoingTrans; microstep(refsToTransitions(outgoingTrans->transitionRefs)); // assert(isLegalConfiguration(_configuration)); _perfMicroStepProcessed++; _perfMicroStepTotal++; // if outgoing transition is spontaneous, add number of events to chain if (outgoingTrans->isEventless && !_skipEventChainCalculations && (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) { outgoingTrans->eventsChainRaised = MIN(incomingTrans->eventsChainRaised + outgoingTrans->eventsRaised, UNDECIDABLE); outgoingTrans->eventsChainSent = MIN(incomingTrans->eventsChainSent + outgoingTrans->eventsSent, UNDECIDABLE); if (outgoingTrans->eventsChainRaised > _maxEventRaisedChain) _maxEventRaisedChain = outgoingTrans->eventsChainRaised; if (outgoingTrans->eventsChainSent > _maxEventSentChain) _maxEventSentChain = outgoingTrans->eventsChainSent; } statesRemaining.push_back(std::make_pair(outgoingTrans, new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this))); // add all invokers for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { NodeSet invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); for (unsigned int j = 0; j < invokes.size(); j++) { invoke(Element(invokes[j])); } } _statesToInvoke = NodeSet(); // remember that the last transition lead here outgoingTrans->destination = statesRemaining.back().second->stateId; outgoingTrans->activeDestination = statesRemaining.back().second->activeId; // reset state for next transition set _configuration = globalState->getActiveStates(); _alreadyEntered = globalState->getAlreadyEnteredStates(); _historyValue = globalState->getHistoryStates(); } } } void ChartToFSM::updateRaisedAndSendChains(GlobalState* state, GlobalTransition* source, std::set visited) { for (std::list::iterator transIter = state->sortedOutgoing.begin(); transIter != state->sortedOutgoing.end(); transIter++) { GlobalTransition* transition = *transIter; if (!transition->isEventless) continue; // we do not care for eventful transitions // source leads to spontaneous transition -> update event chains bool eventChainsNeedUpdated = false; if (visited.find(transition) != visited.end()) { // potential spontaneous transition cycle! if (transition->eventsChainRaised > 0) _maxEventRaisedChain = UNDECIDABLE; if (transition->eventsChainSent > 0) _maxEventSentChain = UNDECIDABLE; return; } // UNDECIDABLE means "undecidable / endless" // will source increase our event chain? if (transition->eventsChainRaised != UNDECIDABLE && transition->eventsChainRaised < source->eventsChainRaised + transition->eventsRaised) { // taking transition after source causes more events in chain transition->eventsChainRaised = MIN(source->eventsChainRaised + transition->eventsRaised, UNDECIDABLE); eventChainsNeedUpdated = true; } if (transition->eventsChainSent != UNDECIDABLE && transition->eventsChainSent < source->eventsChainSent + transition->eventsSent) { // taking transition after source causes more events in chain transition->eventsChainSent = MIN(source->eventsChainSent + transition->eventsSent, UNDECIDABLE); eventChainsNeedUpdated = true; } if (eventChainsNeedUpdated && transition->destination.length() > 0 && _globalConf.find(transition->destination) != _globalConf.end()) { visited.insert(transition); // iterate all spontaneous transitions in destination and update event chains updateRaisedAndSendChains(_globalConf[transition->destination], transition, visited); } if (transition->eventsChainRaised > _maxEventRaisedChain) _maxEventRaisedChain = transition->eventsChainRaised; if (transition->eventsChainSent > _maxEventSentChain) _maxEventSentChain = transition->eventsChainSent; } } uint32_t ChartToFSM::getMinInternalQueueLength(uint32_t defaultVal) { if (_maxEventRaisedChain != UNDECIDABLE) return _maxEventRaisedChain + _doneEventRaiseTolerance; return defaultVal; } uint32_t ChartToFSM::getMinExternalQueueLength(uint32_t defaultVal) { if (_maxEventSentChain != UNDECIDABLE) return _maxEventSentChain; return defaultVal; } void ChartToFSM::reassembleFromFlat() { LOG(ERROR) << "Cannot flatten flat SCXML document"; abort(); } Arabica::XPath::NodeSet ChartToFSM::refsToStates(const std::set& stateRefs) { NodeSet states; for (std::set::const_iterator stateIter = stateRefs.begin(); stateIter != stateRefs.end(); stateIter++) { states.push_back(indexedStates[*stateIter]); } return states; } Arabica::XPath::NodeSet ChartToFSM::refsToTransitions(const std::set& transRefs) { NodeSet transitions; for (std::set::const_iterator transIter = transRefs.begin(); transIter != transRefs.end(); transIter++) { transitions.push_back(indexedTransitions[*transIter]); } return transitions; } void ChartToFSM::beforeMicroStep(Interpreter interpreter) { } void ChartToFSM::onStableConfiguration(Interpreter interpreter) { } void ChartToFSM::beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { GlobalTransition::Action action; action.exited = state; _currGlobalTransition->actions.push_back(action); } void ChartToFSM::beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { GlobalTransition::Action action; action.entered = state; _currGlobalTransition->actions.push_back(action); } void ChartToFSM::beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) { } std::ostream& operator<< (std::ostream& os, const GlobalTransition::Action& action) { if (action.onEntry) os << "onEntry: " << action.onEntry; if (action.onExit) os << "onExit: " << action.onExit; if (action.transition) os << "transition: " << action.transition; if (action.entered) os << "entered: " << action.entered; if (action.exited) os << "exited: " << action.exited; if (action.invoke) os << "invoke: " << action.invoke; if (action.uninvoke) os << "uninvoke: " << action.uninvoke; if (action.raiseDone) os << "raiseDone: " << action.raiseDone; return os; } GlobalState::GlobalState(const Arabica::XPath::NodeSet& activeStates_, const Arabica::XPath::NodeSet& alreadyEnteredStates_, // we need to remember for binding=late const std::map >& historyStates_, const std::string& xmlNSPrefix, ChartToFSM* flattener) { interpreter = flattener; activeIndex = -1; // take references for (size_t i = 0; i < activeStates_.size(); i++) { activeStatesRefs.insert(strTo(ATTR_CAST(activeStates_[i], "index"))); } for (size_t i = 0; i < alreadyEnteredStates_.size(); i++) { alreadyEnteredStatesRefs.insert(strTo(ATTR_CAST(alreadyEnteredStates_[i], "index"))); } for (std::map >::const_iterator histIter = historyStates_.begin(); histIter != historyStates_.end(); histIter++) { for (size_t i = 0; i < histIter->second.size(); i++) { historyStatesRefs[histIter->first].insert(strTo(ATTR_CAST(histIter->second[i], "index"))); } } isFinal = false; // is state this final? for(int i = 0; i < activeStates_.size(); i++) { Arabica::DOM::Element state = Arabica::DOM::Element(activeStates_[i]); Arabica::DOM::Element parentElem = (Arabica::DOM::Element)state.getParentNode(); if(InterpreterImpl::isFinal(state) && iequals(parentElem.getTagName(), xmlNSPrefix + "scxml")) { isFinal = true; break; } } FlatStateIdentifier flatStateId(getActiveStates(), getAlreadyEnteredStates(), getHistoryStates()); stateId = flatStateId.getStateId(); activeId = flatStateId.getFlatActive(); } GlobalTransition* GlobalTransition::copyWithoutExecContent(GlobalTransition* other) { GlobalTransition* newTrans = new GlobalTransition(*other); newTrans->actions.clear(); newTrans->historyBase = other; other->historyTrans.push_back(newTrans); return newTrans; } GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& transitionSet, DataModel dataModel, ChartToFSM* flattener) { interpreter = flattener; eventsRaised = 0; eventsSent = 0; eventsChainRaised = 0; eventsChainSent = 0; historyBase = NULL; for (size_t i = 0; i < transitionSet.size(); i++) { transitionRefs.insert(strTo(ATTR_CAST(transitionSet[i], "index"))); } std::ostringstream setId; // also build id for subset std::string seperator = ""; for (std::set::iterator transIter = transitionRefs.begin(); transIter != transitionRefs.end(); transIter++) { setId << seperator << *transIter; seperator = "-"; } transitionId = setId.str(); hasExecutableContent = false; isValid = true; isEventless = true; #if 0 std::cerr << "################" << std::endl; for (size_t i = 0; i < transitions.size(); i++) { std::cerr << transitions[i] << std::endl; } std::cerr << "################" << std::endl; #endif // first establish whether this is a valid set /** * Can these events event occur together? They can't if: * 1. event / eventless is mixed * 2. target / targetless is mixed (?) * 3. there is no common prefix for their event attribute */ bool foundWithEvent = false; bool foundEventLess = false; bool foundWithTarget = false; bool foundTargetLess = false; Arabica::DOM::Element withEvent; Arabica::DOM::Element noneEvent; Arabica::DOM::Element withTarget; Arabica::DOM::Element noneTarget; for (size_t i = 0; i < transitionSet.size(); i++) { Arabica::DOM::Element transElem = Arabica::DOM::Element(transitionSet[i]); if (HAS_ATTR(transElem, "eventexpr")) { ERROR_EXECUTION_THROW("Cannot flatten document with eventexpr attributes"); } if (HAS_ATTR(transElem, "event")) { foundWithEvent = true; withEvent = transElem; if (foundEventLess) break; } else { foundEventLess = true; noneEvent = transElem; if (foundWithEvent) break; } if (HAS_ATTR(transElem, "target")) { foundWithTarget = true; withTarget = transElem; if (foundTargetLess) break; } else { foundTargetLess = true; noneTarget = transElem; if (foundWithTarget) break; } } // do not mix eventless and event transitions if (foundEventLess && foundWithEvent) { if (flattener->_keepInvalidTransitions) { invalidReason = MIXES_EVENT_SPONTANEOUS; invalidMsg = "Mixes (non-)spontaneous"; } isValid = false; return; } // 403c vs 229 / 403b - solved via filterChildEnabled if (foundTargetLess && foundWithTarget) { // isValid = false; // return; } isEventless = foundEventLess; isTargetless = !foundWithTarget; // is there a set of event names that would enable this conflict-free transition set? if (foundWithEvent) { // get the set of longest event descriptors that will enable this transition set eventNames = getCommonEvents(transitionSet); if (eventNames.size() == 0) { // LOG(INFO) << "No event will activate this conflict-free subset" << std::endl; if (flattener->_keepInvalidTransitions) { invalidReason = NO_COMMON_EVENT; invalidMsg = "No common event"; } isValid = false; return; } else { std::string seperator = ""; for (std::list::iterator eventIter = eventNames.begin(); eventIter != eventNames.end(); eventIter++) { eventDesc += seperator + *eventIter; seperator = " "; } } if (eventDesc.size() == 0) eventDesc = "*"; } // extract conditions and history targets std::list conditions; for (size_t i = 0; i < transitionSet.size(); i++) { Arabica::DOM::Element transElem = Arabica::DOM::Element(transitionSet[i]); // gather conditions while we are iterating anyway if (HAS_ATTR(transElem, "cond")) { conditions.push_back(boost::trim_copy(ATTR(transElem, "cond"))); } std::list targets = tokenize(ATTR(transElem, "target")); std::list::iterator targetIter = targets.begin(); while(targetIter != targets.end()) { // std::cout << "// " << *targetIter << std::endl; if (flattener->_historyTargets.find(*targetIter) != flattener->_historyTargets.end()) { histTargets.insert(*targetIter); } targetIter++; } // std::cout << std::endl << std::endl; } seperator = ""; for (std::vector >::iterator transIter = interpreter->indexedTransitions.begin(); transIter != interpreter->indexedTransitions.end(); transIter++) { const Element& refTrans = *transIter; if (!HAS_ATTR(refTrans, "priority")) continue; if (InterpreterImpl::isMember(refTrans, transitionSet)) { members += seperator + ATTR(refTrans, "priority"); } else { members += seperator; for (size_t i = 0; i < ATTR(refTrans, "priority").size(); i++) { members += " "; } } seperator = " "; } // if (members == " 4 6 7 ") // std::cout << "asdfadf"; if (conditions.size() > 1) { condition = dataModel.andExpressions(conditions); if (condition.size() == 0) { LOG(ERROR) << "Datamodel does not support to conjungate expressions!" << std::endl; } } else if (conditions.size() == 1) { condition = conditions.front(); } } Arabica::XPath::NodeSet GlobalState::getActiveStates() { return interpreter->refsToStates(activeStatesRefs); } Arabica::XPath::NodeSet GlobalState::getAlreadyEnteredStates() { return interpreter->refsToStates(alreadyEnteredStatesRefs); } std::map > GlobalState::getHistoryStates() { std::map > historyValue; for (std::map >::iterator histIter = historyStatesRefs.begin(); histIter != historyStatesRefs.end(); histIter++) { historyValue[histIter->first] = interpreter->refsToStates(histIter->second); } return historyValue; } Arabica::XPath::NodeSet GlobalTransition::getTransitions() const { return interpreter->refsToTransitions(transitionRefs); } std::list GlobalTransition::getCommonEvents(const NodeSet& transitions) { std::list prefixes; std::list longestPrefixes; for (size_t i = 0; i < transitions.size(); i++) { // for every transition std::list eventNames = tokenize(ATTR_CAST(transitions[i], "event")); for (std::list::iterator eventNameIter = eventNames.begin(); eventNameIter != eventNames.end(); eventNameIter++) { // for every event descriptor std::string eventName = *eventNameIter; // remove trailing .* if (eventName.find("*", eventName.size() - 1) != std::string::npos) eventName = eventName.substr(0, eventName.size() - 1); if (eventName.find(".", eventName.size() - 1) != std::string::npos) eventName = eventName.substr(0, eventName.size() - 1); bool isMatching = true; for (size_t j = 0; j < transitions.size(); j++) { // check if token would activate all other transitions if (i == j) continue; if (!nameMatch(ATTR_CAST(transitions[j], "event"), eventName)) { isMatching = false; break; } } if (isMatching) { prefixes.push_back(eventName); } } } // from the set of event names, remove those that are prefixes for (std::list::iterator outerEventNameIter = prefixes.begin(); outerEventNameIter != prefixes.end(); outerEventNameIter++) { for (std::list::iterator innerEventNameIter = prefixes.begin(); innerEventNameIter != prefixes.end(); innerEventNameIter++) { if (!iequals(*outerEventNameIter, *innerEventNameIter) && nameMatch(*outerEventNameIter, *innerEventNameIter)) { goto IS_PREFIX; } } longestPrefixes.push_back(*outerEventNameIter); IS_PREFIX: ; } return longestPrefixes; } }