diff options
author | Stefan Radomski <radomski@tk.informatik.tu-darmstadt.de> | 2014-07-30 20:41:50 (GMT) |
---|---|---|
committer | Stefan Radomski <radomski@tk.informatik.tu-darmstadt.de> | 2014-07-30 20:41:50 (GMT) |
commit | b7a2d38bdcee3bf85a32dea7ac74b144d5ef40fa (patch) | |
tree | bade629bcca6b6a1417cb45be4349a3c27ea0feb /src/uscxml | |
parent | afbd0c4463c6f28ec1cd6ea45a68fdbcfcfeae6c (diff) | |
download | uscxml-b7a2d38bdcee3bf85a32dea7ac74b144d5ef40fa.zip uscxml-b7a2d38bdcee3bf85a32dea7ac74b144d5ef40fa.tar.gz uscxml-b7a2d38bdcee3bf85a32dea7ac74b144d5ef40fa.tar.bz2 |
See detailled log
- Forcing Data.Type for Data(String) constructor now, default used to be INTERPRETED.
- setDataModel and addIOProcessor on interpreter now
- fixed a bug with Data(bool) constructor
- various smaller fixes
Diffstat (limited to 'src/uscxml')
24 files changed, 1414 insertions, 345 deletions
diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 8767242..438aec3 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -353,6 +353,7 @@ InterpreterImpl::InterpreterImpl() { _topLevelFinalReached = false; _stable = false; _isInitialized = false; + _userSuppliedDataModel = false; _domIsSetup = false; _httpServlet = NULL; _factory = NULL; @@ -723,6 +724,7 @@ void InterpreterImpl::setupDOM() { // normalize document // TODO: Resolve XML includes +#if 0 // make sure every state has an id Arabica::XPath::NodeSet<std::string> states; states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "state", _scxml).asNodeSet()); @@ -734,6 +736,7 @@ void InterpreterImpl::setupDOM() { stateElem.setAttribute("id", UUID::getUUID()); } } +#endif // make sure every invoke has an idlocation or id Arabica::XPath::NodeSet<std::string> invokes = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "invoke", _scxml).asNodeSet(); @@ -744,10 +747,12 @@ void InterpreterImpl::setupDOM() { } } +#if 0 // add an id to the scxml element if (!_scxml.hasAttribute("id")) { _scxml.setAttribute("id", UUID::getUUID()); } +#endif // register for dom events to manage cached states Arabica::DOM::Events::EventTarget<std::string> eventTarget(_scxml); @@ -779,12 +784,14 @@ void InterpreterImpl::init() { // start io processoes setupIOProcessors(); - // instantiate datamodel - if (HAS_ATTR(_scxml, "datamodel")) { - // might throw - _dataModel = _factory->createDataModel(ATTR(_scxml, "datamodel"), this); - } else { - _dataModel = _factory->createDataModel("null", this); + // instantiate datamodel if not explicitly set + if (!_dataModel) { + if (HAS_ATTR(_scxml, "datamodel")) { + // might throw + _dataModel = _factory->createDataModel(ATTR(_scxml, "datamodel"), this); + } else { + _dataModel = _factory->createDataModel("null", this); + } } _dataModel.assign("_x.args", _cmdLineOptions); @@ -820,9 +827,7 @@ void InterpreterImpl::init() { // executeGlobalScriptElements NodeSet<std::string> globalScriptElems = filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml); for (unsigned int i = 0; i < globalScriptElems.size(); i++) { - if (_dataModel) { - executeContent(Element<std::string>(globalScriptElems[i])); - } + executeContent(Element<std::string>(globalScriptElems[i])); } _isInitialized = true; @@ -833,10 +838,6 @@ void InterpreterImpl::init() { * Called with a single data element from the topmost datamodel element. */ void InterpreterImpl::initializeData(const Element<std::string>& data) { - if (!_dataModel) { - LOG(ERROR) << "Cannot initialize data when no datamodel is given!"; - return; - } /// test 226/240 - initialize from invoke request if (_invokeReq.params.find(ATTR(data, "id")) != _invokeReq.params.end()) { @@ -893,7 +894,9 @@ void InterpreterImpl::internalDoneSend(const Arabica::DOM::Element<std::string>& if (!isState(state)) return; - Arabica::DOM::Element<std::string> parent = (Arabica::DOM::Element<std::string>)state.getParentNode(); + if (parentIsScxmlState(state)) + return; + Event event; Arabica::XPath::NodeSet<std::string> doneDatas = filterChildElements(_nsInfo.xmlNSPrefix + "donedata", state); @@ -907,7 +910,7 @@ void InterpreterImpl::internalDoneSend(const Arabica::DOM::Element<std::string>& if (contents.size() > 0) { std::string expr; processContentElement(contents[0], event.dom, event.content, expr); - if (expr.length() > 0 && _dataModel) { + if (expr.length() > 0) { try { event.content =_dataModel.evalAsString(expr); } catch (Event e) { @@ -942,7 +945,7 @@ void InterpreterImpl::processDOMorText(const Arabica::DOM::Node<std::string>& el std::string& text) { // do we need to download? if (HAS_ATTR(element, "src") || - (HAS_ATTR(element, "srcexpr") && _dataModel)) { + (HAS_ATTR(element, "srcexpr"))) { std::stringstream srcContent; URL sourceURL(HAS_ATTR(element, "srcexpr") ? _dataModel.evalAsString(ATTR(element, "srcexpr")) : ATTR(element, "src")); if (!sourceURL.toAbsolute(_baseURI)) { @@ -1042,9 +1045,9 @@ void InterpreterImpl::processParamChilds(const Arabica::DOM::Node<std::string>& continue; } Data paramValue; - if (HAS_ATTR(paramElems[i], "expr") && _dataModel) { + if (HAS_ATTR(paramElems[i], "expr")) { paramValue = _dataModel.getStringAsData(ATTR(paramElems[i], "expr")); - } else if(HAS_ATTR(paramElems[i], "location") && _dataModel) { + } else if(HAS_ATTR(paramElems[i], "location")) { paramValue = _dataModel.getStringAsData(ATTR(paramElems[i], "location")); } else { LOG(ERROR) << "param element is missing expr or location or no datamodel is specified"; @@ -1072,7 +1075,7 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { sendReq.Event::eventType = Event::EXTERNAL; try { // event - if (HAS_ATTR(element, "eventexpr") && _dataModel) { + if (HAS_ATTR(element, "eventexpr")) { sendReq.name = _dataModel.evalAsString(ATTR(element, "eventexpr")); } else if (HAS_ATTR(element, "event")) { sendReq.name = ATTR(element, "event"); @@ -1083,7 +1086,7 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { } try { // target - if (HAS_ATTR(element, "targetexpr") && _dataModel) { + if (HAS_ATTR(element, "targetexpr")) { sendReq.target = _dataModel.evalAsString(ATTR(element, "targetexpr")); } else if (HAS_ATTR(element, "target")) { sendReq.target = ATTR(element, "target"); @@ -1094,7 +1097,7 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { } try { // type - if (HAS_ATTR(element, "typeexpr") && _dataModel) { + if (HAS_ATTR(element, "typeexpr")) { sendReq.type = _dataModel.evalAsString(ATTR(element, "typeexpr")); } else if (HAS_ATTR(element, "type")) { sendReq.type = ATTR(element, "type"); @@ -1128,8 +1131,8 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { * */ sendReq.sendid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); - if (HAS_ATTR(element, "idlocation") && _dataModel) { - _dataModel.assign(ATTR(element, "idlocation"), "'" + sendReq.sendid + "'"); + if (HAS_ATTR(element, "idlocation")) { + _dataModel.assign(ATTR(element, "idlocation"), Data("'" + sendReq.sendid + "'", Data::INTERPRETED)); } else { sendReq.hideSendId = true; } @@ -1143,7 +1146,7 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { // delay std::string delay; sendReq.delayMs = 0; - if (HAS_ATTR(element, "delayexpr") && _dataModel) { + if (HAS_ATTR(element, "delayexpr")) { delay = _dataModel.evalAsString(ATTR(element, "delayexpr")); } else if (HAS_ATTR(element, "delay")) { delay = ATTR(element, "delay"); @@ -1202,7 +1205,7 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { if (contents.size() > 0) { std::string expr; processContentElement(contents[0], sendReq.dom, sendReq.content, expr); - if (expr.length() > 0 && _dataModel) { + if (expr.length() > 0) { try { sendReq.data = _dataModel.getStringAsData(expr); } catch (Event e) { @@ -1279,7 +1282,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { invokeReq.Event::eventType = Event::EXTERNAL; try { // type - if (HAS_ATTR(element, "typeexpr") && _dataModel) { + if (HAS_ATTR(element, "typeexpr")) { invokeReq.type = _dataModel.evalAsString(ATTR(element, "typeexpr")); } else if (HAS_ATTR(element, "type")) { invokeReq.type = ATTR(element, "type"); @@ -1287,7 +1290,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { // src std::string source; - if (HAS_ATTR(element, "srcexpr") && _dataModel) { + if (HAS_ATTR(element, "srcexpr")) { source = _dataModel.evalAsString(ATTR(element, "srcexpr")); } else if (HAS_ATTR(element, "src")) { source = ATTR(element, "src"); @@ -1307,8 +1310,8 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { invokeReq.invokeid = ATTR(element, "id"); } else { invokeReq.invokeid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); - if (HAS_ATTR(element, "idlocation") && _dataModel) { - _dataModel.assign(ATTR(element, "idlocation"), "'" + invokeReq.invokeid + "'"); + if (HAS_ATTR(element, "idlocation")) { + _dataModel.assign(ATTR(element, "idlocation"), Data("'" + invokeReq.invokeid + "'", Data::INTERPRETED)); } } } catch (Event e) { @@ -1352,7 +1355,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { if (contents.size() > 0) { std::string expr; processContentElement(contents[0], invokeReq.dom, invokeReq.content, expr); - if (expr.length() > 0 && _dataModel) { + if (expr.length() > 0) { try { invokeReq.data =_dataModel.getStringAsData(expr); } catch (Event e) { @@ -1414,12 +1417,10 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { } catch(...) { LOG(ERROR) << "Unknown exception caught while sending invoke request to invoker " << invokeReq.invokeid; } - if (_dataModel) { - try { + try { // _dataModel.assign("_invokers['" + invokeReq.invokeid + "']", invoker.getDataModelVariables()); - } catch(...) { - LOG(ERROR) << "Exception caught while assigning datamodel variables from invoker " << invokeReq.invokeid; - } + } catch(...) { + LOG(ERROR) << "Exception caught while assigning datamodel variables from invoker " << invokeReq.invokeid; } } catch (...) { LOG(ERROR) << "Invoker " << invokeReq.type << " threw an exception"; @@ -1435,7 +1436,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { void InterpreterImpl::cancelInvoke(const Arabica::DOM::Node<std::string>& element) { std::string invokeId; - if (HAS_ATTR(element, "idlocation") && _dataModel) { + if (HAS_ATTR(element, "idlocation")) { invokeId = _dataModel.evalAsString(ATTR(element, "idlocation")); } else if (HAS_ATTR(element, "id")) { invokeId = ATTR(element, "id"); @@ -1444,12 +1445,10 @@ void InterpreterImpl::cancelInvoke(const Arabica::DOM::Node<std::string>& elemen } if (_invokers.find(invokeId) != _invokers.end()) { LOG(INFO) << "Removed invoker at " << invokeId; - if (_dataModel) { - try { - _dataModel.assign("_invokers['" + invokeId + "']", std::string("''")); - } catch (Event e) { - LOG(ERROR) << "Syntax when removing invoker:" << std::endl << e << std::endl; - } + try { + _dataModel.assign("_invokers['" + invokeId + "']", Data(std::string("''"), Data::INTERPRETED)); + } catch (Event e) { + LOG(ERROR) << "Syntax when removing invoker:" << std::endl << e << std::endl; } USCXML_MONITOR_CALLBACK3(beforeUninvoking, Element<std::string>(element), invokeId) @@ -1464,8 +1463,62 @@ void InterpreterImpl::cancelInvoke(const Arabica::DOM::Node<std::string>& elemen //receiveInternal(Event("done.invoke." + invokeId, Event::PLATFORM)); } +#if 1 // see: http://www.w3.org/TR/scxml/#EventDescriptors +bool InterpreterImpl::nameMatch(const std::string& eventDescs, const std::string& eventName) { + if(eventDescs.length() == 0 || eventName.length() == 0) + return false; + + // naive case of single descriptor and exact match + if (iequals(eventDescs, eventName)) + return true; + + size_t start = 0; + std::string eventDesc; + for (int i = 0; i < eventDescs.size(); i++) { + if (isspace(eventDescs[i])) { + if (i > 0 && start < i - 1) { + eventDesc = eventDescs.substr(start, i - start); + } + while(isspace(eventDescs[++i])); // skip whitespaces + start = i; + } else if (i + 1 == eventDescs.size()) { + eventDesc = eventDescs.substr(start, i + 1 - start); + } + + if (eventDesc.size() > 0) { + // remove optional trailing .* for CCXML compatibility + if (eventDesc.find("*", eventDesc.size() - 1) != std::string::npos) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + if (eventDesc.find(".", eventDesc.size() - 1) != std::string::npos) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + + // was eventDesc the * wildcard + if (eventDesc.size() == 0) + return true; + + // eventDesc has to be a real prefix of event now and therefore shorter + if (eventDesc.size() >= eventName.size()) + goto NEXT_DESC; + + // are they already equal? + if (iequals(eventDesc, eventName)) + return true; + + if (eventName.find(eventDesc) == 0) { + if (eventName.find(".", eventDesc.size()) == eventDesc.size()) + return true; + } +NEXT_DESC: + eventDesc = ""; + } + } + return false; +} + +#else +// see: http://www.w3.org/TR/scxml/#EventDescriptors bool InterpreterImpl::nameMatch(const std::string& transitionEvent, const std::string& event) { if(transitionEvent.length() == 0 || event.length() == 0) return false; @@ -1507,7 +1560,7 @@ bool InterpreterImpl::nameMatch(const std::string& transitionEvent, const std::s } return false; } - +#endif bool InterpreterImpl::hasConditionMatch(const Arabica::DOM::Element<std::string>& conditional) { if (HAS_ATTR(conditional, "cond") && ATTR(conditional, "cond").length() > 0) { @@ -1588,7 +1641,7 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Element<std::string>& c if (contents.size() > 0) { std::string expr; processContentElement(contents[0], raised.dom, raised.content, expr); - if (expr.length() > 0 && _dataModel) { + if (expr.length() > 0) { try { raised.data = _dataModel.getStringAsData(expr); } catch (Event e) { @@ -1657,33 +1710,31 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Element<std::string>& c std::cerr << "Found single else to evaluate!" << std::endl; } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "foreach")) { // --- FOREACH -------------------------- - if (_dataModel) { - if (HAS_ATTR(content, "array") && HAS_ATTR(content, "item")) { - 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; - try { - iterations = _dataModel.getLength(array); - } - CATCH_AND_DISTRIBUTE2("Syntax error in array attribute of foreach element", content) - try { - _dataModel.pushContext(); // copy old and enter new context + if (HAS_ATTR(content, "array") && HAS_ATTR(content, "item")) { + 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; + try { + iterations = _dataModel.getLength(array); + } + CATCH_AND_DISTRIBUTE2("Syntax error in array attribute of foreach element", content) + try { + _dataModel.pushContext(); // copy old and enter new context // if (!_dataModel.isDeclared(item)) { // _dataModel.init(item, Data()); // } - for (uint32_t iteration = 0; iteration < iterations; iteration++) { - _dataModel.setForeach(item, array, index, iteration); - if (content.hasChildNodes()) - // execute content and have exception rethrown to break foreach - executeContent(content.getChildNodes(), rethrow); - } - _dataModel.popContext(); // leave stacked context + for (uint32_t iteration = 0; iteration < iterations; iteration++) { + _dataModel.setForeach(item, array, index, iteration); + if (content.hasChildNodes()) + // execute content and have exception rethrown to break foreach + executeContent(content.getChildNodes(), rethrow); } - CATCH_AND_DISTRIBUTE2("Syntax error in foreach element", content) - } else { - LOG(ERROR) << "Expected array and item attributes with foreach element!" << std::endl; + _dataModel.popContext(); // leave stacked context } + CATCH_AND_DISTRIBUTE2("Syntax error in foreach element", content) + } else { + LOG(ERROR) << "Expected array and item attributes with foreach element!" << std::endl; } } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "log")) { // --- LOG -------------------------- @@ -1701,7 +1752,7 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Element<std::string>& c } } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "assign")) { // --- ASSIGN -------------------------- - if (_dataModel && HAS_ATTR(content, "location")) { + if (HAS_ATTR(content, "location")) { try { if (!_dataModel.isDeclared(ATTR(content, "location"))) { // test 286, 331 @@ -1717,64 +1768,60 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Element<std::string>& c } } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "validate")) { // --- VALIDATE -------------------------- - if (_dataModel) { - std::string location = (HAS_ATTR(content, "location") ? ATTR(content, "location") : ""); - std::string schema = (HAS_ATTR(content, "schema") ? ATTR(content, "schema") : ""); - _dataModel.validate(location, schema); - } + std::string location = (HAS_ATTR(content, "location") ? ATTR(content, "location") : ""); + std::string schema = (HAS_ATTR(content, "schema") ? ATTR(content, "schema") : ""); + _dataModel.validate(location, schema); } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "script")) { // --- SCRIPT -------------------------- - if (_dataModel) { - if (HAS_ATTR(content, "src")) { - URL scriptUrl(ATTR(content, "src")); - if (!scriptUrl.isAbsolute() && !_baseURI) { - LOG(ERROR) << "script element has relative URI " << ATTR(content, "src") << " with no base URI set for interpreter"; - return; - } + if (HAS_ATTR(content, "src")) { + URL scriptUrl(ATTR(content, "src")); + if (!scriptUrl.isAbsolute() && !_baseURI) { + LOG(ERROR) << "script element has relative URI " << ATTR(content, "src") << " with no base URI set for interpreter"; + return; + } - if (!scriptUrl.toAbsolute(_baseURI)) { - LOG(ERROR) << "Failed to convert relative script URI " << ATTR(content, "src") << " to absolute with base URI " << _baseURI.asString(); - return; - } + if (!scriptUrl.toAbsolute(_baseURI)) { + LOG(ERROR) << "Failed to convert relative script URI " << ATTR(content, "src") << " to absolute with base URI " << _baseURI.asString(); + return; + } - std::stringstream srcContent; - try { - if (_cachedURLs.find(scriptUrl.asString()) != _cachedURLs.end() && false) { - srcContent << _cachedURLs[scriptUrl.asString()]; - } else { - srcContent << scriptUrl; - if (scriptUrl.downloadFailed()) { - LOG(ERROR) << "script element source cannot be downloaded"; - } - _cachedURLs[scriptUrl.asString()] = scriptUrl; - } - } catch (Event exception) { - // script failed to download - if (exception.name == "error.communication") { - throw exception; // terminate test329, test301 + std::stringstream srcContent; + try { + if (_cachedURLs.find(scriptUrl.asString()) != _cachedURLs.end() && false) { + srcContent << _cachedURLs[scriptUrl.asString()]; + } else { + srcContent << scriptUrl; + if (scriptUrl.downloadFailed()) { + LOG(ERROR) << "script element source cannot be downloaded"; } - receive(exception); - return; + _cachedURLs[scriptUrl.asString()] = scriptUrl; + } + } catch (Event exception) { + // script failed to download + if (exception.name == "error.communication") { + throw exception; // terminate test329, test301 } + receive(exception); + return; + } - try { - _dataModel.eval((Element<std::string>)content, srcContent.str()); + try { + _dataModel.eval((Element<std::string>)content, srcContent.str()); + } + CATCH_AND_DISTRIBUTE("Syntax error while executing script element from '" + ATTR(content, "src") + "':") + } else { + if (content.hasChildNodes()) { + // search for the text node with the actual script + std::string scriptContent; + for (Node<std::string> child = content.getFirstChild(); child; child = child.getNextSibling()) { + if (child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE) + scriptContent += child.getNodeValue(); } - CATCH_AND_DISTRIBUTE("Syntax error while executing script element from '" + ATTR(content, "src") + "':") - } else { - if (content.hasChildNodes()) { - // search for the text node with the actual script - std::string scriptContent; - for (Node<std::string> child = content.getFirstChild(); child; child = child.getNextSibling()) { - if (child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE) - scriptContent += child.getNodeValue(); - } - if (scriptContent.size() > 0) { - try { - _dataModel.eval((Element<std::string>)content, scriptContent); - } - CATCH_AND_DISTRIBUTE2("Syntax error while executing script element", content) + if (scriptContent.size() > 0) { + try { + _dataModel.eval((Element<std::string>)content, scriptContent); } + CATCH_AND_DISTRIBUTE2("Syntax error while executing script element", content) } } } @@ -2281,9 +2328,8 @@ bool InterpreterImpl::isDescendant(const Arabica::DOM::Node<std::string>& s1, } bool InterpreterImpl::isTargetless(const Arabica::DOM::Element<std::string>& transition) { - if (transition.hasAttributes()) { - if (((Arabica::DOM::Element<std::string>)transition).hasAttribute("target")) - return false; + if (transition.hasAttribute("target")) { + return false; } return true; } @@ -2428,6 +2474,12 @@ void InterpreterImpl::setupIOProcessors() { continue; } + // do not override if already set + if (_ioProcessors.find(ioProcIter->first) != _ioProcessors.end()) { + ioProcIter++; + continue; + } + // this might throw error.execution _ioProcessors[ioProcIter->first] = _factory->createIOProcessor(ioProcIter->first, this); _ioProcessors[ioProcIter->first].setType(ioProcIter->first); @@ -2447,19 +2499,16 @@ void InterpreterImpl::setupIOProcessors() { std::list<std::string> names = _ioProcessors[ioProcIter->first].getNames(); std::list<std::string>::iterator nameIter = names.begin(); while(nameIter != names.end()) { - if (!boost::equal(*nameIter, ioProcIter->first)) + // do not override + if (!boost::equal(*nameIter, ioProcIter->first) && _ioProcessors.find(*nameIter) != _ioProcessors.end()) _ioProcessors[*nameIter] = _ioProcessors[ioProcIter->first]; nameIter++; } #if 0 - if (_dataModel) { - try { - _dataModel.registerIOProcessor(ioProcIter->first, _ioProcessors[ioProcIter->first]); - } catch (Event e) { - LOG(ERROR) << "Syntax error when setting _ioprocessors:" << std::endl << e << std::endl; - } - } else { - LOG(INFO) << "Not registering " << ioProcIter->first << " at _ioprocessors in datamodel, no datamodel specified"; + try { + _dataModel.registerIOProcessor(ioProcIter->first, _ioProcessors[ioProcIter->first]); + } catch (Event e) { + LOG(ERROR) << "Syntax error when setting _ioprocessors:" << std::endl << e << std::endl; } #endif ioProcIter++; @@ -2550,7 +2599,7 @@ bool InterpreterImpl::isLegalConfiguration(const NodeSet<std::string>& config) { for (int i = 0; i < config.size(); i++) { if (isAtomic(Element<std::string>(config[i]))) { Node<std::string> parent = config[i]; - while((parent = parent.getParentNode())) { + while(((parent = parent.getParentNode()) && parent.getNodeType() == Node_base::ELEMENT_NODE)) { if (isState(Element<std::string>(parent)) && (iequals(LOCALNAME(parent), "state") || iequals(LOCALNAME(parent), "parallel"))) { diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 4659b13..8a2b282 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -277,10 +277,6 @@ public: return _httpServlet; } - DataModel getDataModel() { - return _dataModel; - } - void setParentQueue(uscxml::concurrency::BlockingQueue<SendRequest>* parentQueue) { _parentQueue = parentQueue; } @@ -356,10 +352,31 @@ public: return _sendQueue; } + void addIOProcessor(IOProcessor ioProc) { + std::list<std::string> names = ioProc.getNames(); + + std::list<std::string>::iterator nameIter = names.begin(); + while(nameIter != names.end()) { + _ioProcessors[*nameIter] = ioProc; + _ioProcessors[*nameIter].setType(names.front()); + _ioProcessors[*nameIter].setInterpreter(this); + + nameIter++; + } + } + const std::map<std::string, IOProcessor>& getIOProcessors() { return _ioProcessors; } + void setDataModel(const DataModel& dataModel) { + _userSuppliedDataModel = true; + _dataModel = dataModel; + } + DataModel getDataModel() { + return _dataModel; + } + const std::map<std::string, Invoker>& getInvokers() { return _invokers; } @@ -407,7 +424,7 @@ public: static std::list<std::string> tokenizeIdRefs(const std::string& idRefs); static std::string spaceNormalize(const std::string& text); - static bool nameMatch(const std::string& transitionEvent, const std::string& event); + static bool nameMatch(const std::string& eventDescs, const std::string& event); Arabica::DOM::Node<std::string> findLCCA(const Arabica::XPath::NodeSet<std::string>& states); virtual Arabica::XPath::NodeSet<std::string> getProperAncestors(const Arabica::DOM::Node<std::string>& s1, const Arabica::DOM::Node<std::string>& s2); @@ -448,6 +465,7 @@ protected: bool _topLevelFinalReached; bool _isInitialized; bool _domIsSetup; + bool _userSuppliedDataModel; bool _isStarted; bool _isRunning; @@ -625,9 +643,25 @@ public: return _impl->getHTTPServlet(); } + void setDataModel(const DataModel& dataModel) { + _impl->setDataModel(dataModel); + } DataModel getDataModel() { return _impl->getDataModel(); } + + void addIOProcessor(IOProcessor ioProc) { + _impl->addIOProcessor(ioProc); + } + const std::map<std::string, IOProcessor>& getIOProcessors() { + return _impl->getIOProcessors(); + } + + const std::map<std::string, Invoker>& getInvokers() { + return _impl->getInvokers(); + } + + void setParentQueue(uscxml::concurrency::BlockingQueue<SendRequest>* parentQueue) { return _impl->setParentQueue(parentQueue); } @@ -700,14 +734,6 @@ public: return _impl->getDelayQueue(); } - const std::map<std::string, IOProcessor>& getIOProcessors() { - return _impl->getIOProcessors(); - } - - const std::map<std::string, Invoker>& getInvokers() { - return _impl->getInvokers(); - } - bool runOnMainThread(int fps, bool blocking = true) { return _impl->runOnMainThread(fps, blocking); } diff --git a/src/uscxml/URL.cpp b/src/uscxml/URL.cpp index 79a3f65..4e97faa 100644 --- a/src/uscxml/URL.cpp +++ b/src/uscxml/URL.cpp @@ -179,7 +179,7 @@ URLImpl::operator Data() const { data.compound["host"] = Data(_uri.host(), Data::VERBATIM); data.compound["scheme"] = Data(_uri.scheme(), Data::VERBATIM); data.compound["path"] = Data(_uri.path(), Data::VERBATIM); - data.compound["port"] = Data(_uri.port()); + data.compound["port"] = Data(_uri.port(), Data::INTERPRETED); data.compound["isAbsolute"] = Data(_uri.is_absolute()); if (_statusCode.length() > 0) data.compound["statusCode"] = Data(_statusCode, Data::VERBATIM); diff --git a/src/uscxml/debug/SCXMLDotWriter.cpp b/src/uscxml/debug/SCXMLDotWriter.cpp index 16269a9..e833a10 100644 --- a/src/uscxml/debug/SCXMLDotWriter.cpp +++ b/src/uscxml/debug/SCXMLDotWriter.cpp @@ -20,6 +20,7 @@ #include "uscxml/Common.h" #include "uscxml/UUID.h" #include "SCXMLDotWriter.h" +#include "../transform/FlatStateIdentifier.h" #include "uscxml/DOMUtils.h" #include <boost/algorithm/string.hpp> // replace_all #include <iomanip> @@ -44,6 +45,7 @@ SCXMLDotWriter::SCXMLDotWriter(Interpreter interpreter, NodeList<std::string > scxmlElems = interpreter.getDocument().getElementsByTagName("scxml"); _scxml = (Element<std::string>)scxmlElems.item(0); + _isFlat = HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat")); if (_anchors.size() == 0) { StateAnchor anchor; @@ -275,7 +277,7 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Element<std::stri os << getPrefix() << "subgraph \"cluster_" << stateId << "\" {" << std::endl; _indentation++; os << getPrefix() << "fontsize=14" << std::endl; - os << getPrefix() << "label=<<b>" << nameForNode(stateElem) << "</b>>" << std::endl; + os << getPrefix() << "label=<" << nameForNode(stateElem) << ">" << std::endl; // os << getPrefix() << "rank=\"same\"" << std::endl; os << getPrefix() << "labeljust=l" << std::endl; @@ -312,7 +314,7 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Element<std::stri os << getPrefix() << "shape=doublecircle," << std::endl; os << getPrefix() << "color=black," << std::endl; os << getPrefix() << "penwidth=2," << std::endl; - os << getPrefix() << "label=<" << nameForNode(stateElem) << ">" << std::endl; + os << getPrefix() << "label=< <table cellborder=\"0\" border=\"0\"><tr><td balign=\"left\">" << nameForNode(stateElem) << "</td></tr></table>>" << std::endl; _indentation--; os << getPrefix() << "];" << std::endl; @@ -325,27 +327,30 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Element<std::stri // os << getPrefix() << "style=filled, fillcolor=lightgrey, " << std::endl; DotState::mmap_s_e_t::const_iterator destIterF, destIterB; - std::list<std::string> outPorts; // count unique keys + //std::list<std::string> outPorts; // count unique keys + + int nrOutPorts = 0; switch (dotState.portType) { case PORT_TARGET: // outports are per target for(DotState::mmap_s_e_t::const_iterator it = dotState.targets.begin(), end = dotState.targets.end(); it != end; it = dotState.targets.upper_bound(it->first)) { - outPorts.push_back(it->first); + nrOutPorts++; } break; case PORT_EVENT: // outports are per event for(DotState::mmap_s_e_t::const_iterator it = dotState.events.begin(), end = dotState.events.end(); it != end; it = dotState.events.upper_bound(it->first)) { - outPorts.push_back(it->first); + nrOutPorts++; } break; case PORT_TRANSITION: - for (int i = 0; i < dotState.transitions.size(); i++) { - outPorts.push_back(idForNode(dotState.transitions[i])); - } + nrOutPorts = dotState.transitions.size(); +// for (int i = 0; i < dotState.transitions.size(); i++) { +// outPorts.push_back(idForNode(dotState.transitions[i])); +// } break; } @@ -361,26 +366,34 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Element<std::stri */ std::string details = getDetailedLabel(stateElem); + std::string stateLabel = nameForNode(stateElem); + int stateLines = 0; - os << "<table " << (isInitial ? "bgcolor=\"orange\" " : "") << "cellborder=\"1\" border=\"0\" cellspacing=\"0\" cellpadding=\"2\" >" << std::endl; - os << " <tr><td port=\"__name\" rowspan=\"" << outPorts.size() + 1 << "\"><b>" << nameForNode(stateElem) << "</b></td></tr>" << std::endl; + std::string::size_type start = 0; + while ((start = stateLabel.find("<br", start)) != std::string::npos) { + ++stateLines; + start += 3; + } + + os << "<table " << (isInitial ? "bgcolor=\"orange\" " : "") << "valign=\"top\" align=\"left\" cellborder=\"1\" border=\"0\" cellspacing=\"0\" cellpadding=\"5\" >" << std::endl; + os << " <tr><td port=\"__name\" balign=\"left\" valign=\"top\" align=\"left\" rowspan=\"" << nrOutPorts + 1 << "\">" << stateLabel << "</td></tr>" << std::endl; switch (dotState.portType) { case PORT_TARGET: // outports are per target - writePerTargetPorts(os, outPorts, dotState); + writePerTargetPorts(os, dotState, stateLines); break; case PORT_EVENT: // outports are per event - writePerEventPorts(os, outPorts, dotState); + writePerEventPorts(os, dotState, stateLines); break; case PORT_TRANSITION: - writePerTransitionPorts(os, outPorts, dotState); + writePerTransitionPorts(os, dotState, stateLines); break; } // write details of the state if (details.size() > 0) { - os << " <tr><td colspan=\"" << (outPorts.size() == 0 ? 1 : 2) << "\">" << std::endl; + os << " <tr><td balign=\"left\" colspan=\"" << (nrOutPorts == 0 ? 1 : 2) << "\">" << std::endl; os << details << std::endl; os << " </td></tr>" << std::endl; } @@ -388,7 +401,7 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Element<std::stri // write history states NodeSet<std::string> histories = InterpreterImpl::filterChildElements(_xmlNSPrefix + "history", stateElem); for (int i = 0; i < histories.size(); i++) { - os << " <tr><td port=\"" << ATTR(histories[i], "id") << "\" colspan=\"" << (outPorts.size() == 0 ? 1 : 2) << "\"><b>history: </b>" << ATTR(histories[i], "id") << "</td></tr>" << std::endl; + os << " <tr><td port=\"" << ATTR(histories[i], "id") << "\" balign=\"left\" colspan=\"" << (nrOutPorts == 0 ? 1 : 2) << "\"><b>history: </b>" << ATTR(histories[i], "id") << "</td></tr>" << std::endl; } @@ -423,76 +436,92 @@ void SCXMLDotWriter::writeStateElement(std::ostream& os, const Element<std::stri } } -void SCXMLDotWriter::writePerTransitionPorts(std::ostream& os, const std::list<std::string>& outPorts, const DotState& dotState) { +void SCXMLDotWriter::writePerTransitionPorts(std::ostream& os, const DotState& dotState, int stateLines) { // TODO: Not implemented } -void SCXMLDotWriter::writePerEventPorts(std::ostream& os, const std::list<std::string>& outPorts, const DotState& dotState) { - // TODO: Not implemented +void SCXMLDotWriter::writePerEventPorts(std::ostream& os, const DotState& dotState, int stateLines) { + // std::multimap<std::string, Arabica::DOM::Element<std::string> > events; // key is event name, value is transitions that react - // outports contain event names std::string stateId = idForNode(dotState.node); DotState::mmap_s_e_t::const_iterator destIterF, destIterB; - for(std::list<std::string>::const_iterator nameIter = outPorts.begin(); nameIter != outPorts.end(); nameIter++) { - os << " <tr><td port=\"" << portEscape(*nameIter) << "\" align=\"right\">" << *nameIter << "</td></tr>" << std::endl; + for(DotState::mmap_s_e_t::const_iterator it = dotState.events.begin(), end = dotState.events.end(); + it != end; + it = dotState.events.upper_bound(it->first)) { + os << " <tr><td port=\"" << portEscape(it->first) << "\" align=\"right\">" << it->first << "</td></tr>" << std::endl; } + } -void SCXMLDotWriter::writePerTargetPorts(std::ostream& os, const std::list<std::string>& outPorts, const DotState& dotState) { - // outports contain remote node ids +void SCXMLDotWriter::writePerTargetPorts(std::ostream& os, const DotState& dotState, int stateLines) { + // std::multimap<std::string, Arabica::DOM::Element<std::string> > targets; // key is remote node, transition is element + + int nrOutports = 0; std::string stateId = idForNode(dotState.node); - DotState::mmap_s_e_t::const_iterator destIterF, destIterB; - for(std::list<std::string>::const_iterator nameIter = outPorts.begin(); nameIter != outPorts.end(); nameIter++) { + typedef DotState::mmap_s_e_t iter_t; + + // we need to count outports first for vertical padding + for(iter_t::const_iterator targetIter = dotState.targets.begin(), end = dotState.targets.end(); + targetIter != end; + targetIter = dotState.targets.upper_bound(targetIter->first)) { + nrOutports++; + } + + for(iter_t::const_iterator targetIter = dotState.targets.begin(), end = dotState.targets.end(); + targetIter != end; + targetIter = dotState.targets.upper_bound(targetIter->first)) { // gather all events that activate the transition - std::string portName = *nameIter; - DotEdge edge(stateId, portName); - edge.fromPort = portName; - - std::multimap<std::string, std::string> eventConds; // event to condition - std::pair <DotState::mmap_s_e_t::const_iterator, DotState::mmap_s_e_t::const_iterator> targetKeyRange = dotState.targets.equal_range(portName); - for (destIterB = targetKeyRange.first; destIterB != targetKeyRange.second; ++destIterB) { - const Element<std::string>& transElem = destIterB->second; - std::list<std::string> eventNames = InterpreterImpl::tokenizeIdRefs(ATTR(transElem, "event")); - for (std::list<std::string>::iterator eventIter = eventNames.begin(); eventIter != eventNames.end(); eventIter++) { - eventConds.insert(std::make_pair(*eventIter, ATTR(transElem, "cond"))); - } - if (eventNames.size() == 0) { + std::string targetId = targetIter->first; + + std::set<std::string> eventNames; + + DotEdge edge(stateId, targetId); + edge.fromPort = targetId; + + std::pair <iter_t::const_iterator, iter_t::const_iterator> targetKeyRange = dotState.targets.equal_range(targetId); + for (iter_t::const_iterator transIter = targetKeyRange.first; transIter != targetKeyRange.second; ++transIter) { + const Element<std::string>& transElem = transIter->second; + + std::list<std::string> events = InterpreterImpl::tokenizeIdRefs(ATTR(transElem, "event")); + eventNames.insert(events.begin(), events.end()); + + if (events.size() == 0) { // spontaneous transition - eventConds.insert(std::make_pair("#", ATTR(transElem, "cond"))); + eventNames.insert("#"); edge.type = EDGE_SPONTANEOUS; } } - if (_graph.find(portName) != _graph.end()) + + if (_graph.find(targetId) != _graph.end()) _edges.insert(edge); - typedef std::multimap<std::string, std::string>::iterator condIter_t; std::stringstream outPortSS; - outPortSS << "<b>" << portName << "</b><br align=\"right\" />"; + outPortSS << (_isFlat ? FlatStateIdentifier::toHTMLLabel(targetId) : "<b>" + targetId + "</b>" ); - std::string opener = "{"; - std::string closer; - std::string seperator; - condIter_t iterA, iterB; - for(iterA = eventConds.begin(); iterA != eventConds.end(); iterA = iterB) { - std::string eventName = iterA->first; - bool hasCondition = false; - - std::pair <condIter_t, condIter_t> condRange = eventConds.equal_range(eventName); - for (iterB = condRange.first; iterB != condRange.second; ++iterB) { - hasCondition = true; - } + if (_isFlat) { + outPortSS << "<br /><b>events: </b>{"; + } else { + outPortSS << "<br />{"; + } - outPortSS << opener << seperator << eventName << (hasCondition ? "" : ""); + std::string seperator; + for (std::set<std::string>::const_iterator eventIter = eventNames.begin(); eventIter != eventNames.end(); eventIter++) { + outPortSS << seperator << *eventIter << std::endl; seperator = ", "; - opener = ""; - closer = "}"; } - outPortSS << closer; + outPortSS << "}"; - os << " <tr><td port=\"" << portEscape(portName) << "\" align=\"right\">" << outPortSS.str() << "</td></tr>" << std::endl; + if (nrOutports == 1) { + int missing = stateLines - nrOutports; + while (_isFlat && missing-- >= 1) { + outPortSS << "<br /> "; + } + } + + os << " <tr><td port=\"" << portEscape(targetId) << "\" balign=\"left\" align=\"left\">" << outPortSS.str() << "</td></tr>" << std::endl; } } @@ -689,7 +718,6 @@ std::string SCXMLDotWriter::getDetailedLabel(const Element<std::string>& elem, i std::string SCXMLDotWriter::portEscape(const std::string& text) { std::string escaped(text); boost::replace_all(escaped, ".", "-"); - return text; } @@ -719,7 +747,16 @@ std::string SCXMLDotWriter::nameForNode(const Node<std::string>& node) { if (node.getNodeType() == Node_base::ELEMENT_NODE) { Element<std::string> elem = (Element<std::string>)node; - if (false) { + if (InterpreterImpl::isFinal(elem) && _isFlat) { + // ignore visited and history with final elements + FlatStateIdentifier flatId(elem.getAttribute("id")); + return "<b>" + flatId.active.front() + "</b>"; + } + + if (elem.hasAttribute("id") && _isFlat) { + elemName = FlatStateIdentifier::toHTMLLabel(elem.getAttribute("id")); + if (elemName.size() > 0) + return elemName; } else if (elem.getTagName() == "scxml") { if (elem.hasAttribute("name") && !UUID::isUUID(elem.getAttribute("name"))) { elemName += elem.getAttribute("name"); @@ -734,10 +771,11 @@ std::string SCXMLDotWriter::nameForNode(const Node<std::string>& node) { elemName += elem.getAttribute("id"); } } + if (elemName.size() == 0) elemName = boost::lexical_cast<std::string>(node.getLocalName()); - return elemName; + return "<b>" + elemName + "</b>"; } @@ -747,6 +785,13 @@ std::string SCXMLDotWriter::idForNode(const Node<std::string>& node) { // try to get the id as the name or id attribute if (node.getNodeType() == Node_base::ELEMENT_NODE) { Element<std::string> elem = (Element<std::string>)node; + + if (InterpreterImpl::isFinal(elem) && _isFlat) { + // ignore visited and history with final elements + FlatStateIdentifier flatId(elem.getAttribute("id")); + return flatId.activeId(); + } + if (elem.hasAttribute("name")) { elemId = elem.getAttribute("name"); } else if (elem.hasAttribute("id")) { @@ -774,9 +819,6 @@ std::string SCXMLDotWriter::idForNode(const Node<std::string>& node) { } while ((tmpParent = tmpParent.getParentNode())); // elemId = ssElemId.str(); } - - std::replace(elemId.begin(), elemId.end(), '-', '_'); - return elemId; } diff --git a/src/uscxml/debug/SCXMLDotWriter.h b/src/uscxml/debug/SCXMLDotWriter.h index 3ac697f..61fbfad 100644 --- a/src/uscxml/debug/SCXMLDotWriter.h +++ b/src/uscxml/debug/SCXMLDotWriter.h @@ -64,7 +64,7 @@ public: }; struct StateAnchor { - StateAnchor() : childDepth(-1), transDepth(-1), type(PORT_TARGET) {} + StateAnchor() : childDepth(std::numeric_limits<int32_t>::max()), transDepth(std::numeric_limits<int32_t>::max()), type(PORT_TARGET) {} Arabica::DOM::Element<std::string> element; int32_t childDepth; int32_t transDepth; @@ -166,9 +166,9 @@ protected: int32_t transDepth = std::numeric_limits<int32_t>::max()); void writeStateElement(std::ostream& os, const Arabica::DOM::Element<std::string>& state); - void writePerTransitionPorts(std::ostream& os, const std::list<std::string>& outPorts, const DotState& dotState); - void writePerEventPorts(std::ostream& os, const std::list<std::string>& outPorts, const DotState& dotState); - void writePerTargetPorts(std::ostream& os, const std::list<std::string>& outPorts, const DotState& dotState); + void writePerTransitionPorts(std::ostream& os, const DotState& dotState, int stateLines = 0); + void writePerEventPorts(std::ostream& os, const DotState& dotState, int stateLines = 0); + void writePerTargetPorts(std::ostream& os, const DotState& dotState, int stateLines = 0); void writeUnknownNode(std::ostream& os, const std::string& targetId); @@ -184,6 +184,8 @@ protected: Arabica::DOM::Element<std::string> _transition; Arabica::DOM::Element<std::string> _scxml; Interpreter _interpreter; + bool _isFlat; + std::string _xmlNSPrefix; std::list<StateAnchor> _anchors; std::map<std::string, DotEdge> _histories; diff --git a/src/uscxml/interpreter/InterpreterDraft6.cpp b/src/uscxml/interpreter/InterpreterDraft6.cpp index 7569fdb..554cd28 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.cpp +++ b/src/uscxml/interpreter/InterpreterDraft6.cpp @@ -138,6 +138,15 @@ InterpreterState InterpreterDraft6::step(int waitForMS = 0) { setInterpreterState(USCXML_MICROSTEPPED); } + if (!isLegalConfiguration(_configuration)) { + std:: cout << "Illegal configuration: {"; + for (int i = 0; i < _configuration.size(); i++) { + std::cout << ATTR(_configuration[i], "id") << ", " << std::endl; + } + std:: cout << "}" << std::endl; + } + assert(isLegalConfiguration(_configuration)); + // are there spontaneous transitions? if (!_stable) { enabledTransitions = selectEventlessTransitions(); @@ -164,8 +173,7 @@ InterpreterState InterpreterDraft6::step(int waitForMS = 0) { USCXML_MONITOR_CALLBACK2(beforeProcessingEvent, _currEvent) - if (_dataModel) - _dataModel.setEvent(_currEvent); + _dataModel.setEvent(_currEvent); enabledTransitions = selectTransitions(_currEvent.name); if (!enabledTransitions.empty()) { @@ -281,7 +289,7 @@ EXIT_INTERPRETER: _mutex.unlock(); // remove datamodel - if(_dataModel) + if(!_userSuppliedDataModel) _dataModel = DataModel(); setInterpreterState(USCXML_FINISHED); @@ -375,16 +383,14 @@ Arabica::XPath::NodeSet<std::string> InterpreterDraft6::selectTransitions(const unsigned int index = 0; while(states.size() > index) { - bool foundTransition = false; NodeSet<std::string> transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", states[index]); for (unsigned int k = 0; k < transitions.size(); k++) { if (isEnabledTransition(Element<std::string>(transitions[k]), event)) { enabledTransitions.push_back(transitions[k]); - foundTransition = true; goto LOOP; } } - if (!foundTransition) { + { Node<std::string> parent = states[index].getParentNode(); if (parent) { states.push_back(parent); @@ -549,6 +555,9 @@ 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); @@ -559,7 +568,10 @@ bool InterpreterDraft6::isWithinParallel(const Element<std::string>& transition) targets.push_back(source); Node<std::string> lcpa = findLCPA(targets); - return lcpa; + _transWithinParallel[transition] = lcpa; + + return _transWithinParallel[transition]; + } Node<std::string> InterpreterDraft6::findLCPA(const Arabica::XPath::NodeSet<std::string>& states) { diff --git a/src/uscxml/interpreter/InterpreterDraft6.h b/src/uscxml/interpreter/InterpreterDraft6.h index 297434f..062d79a 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.h +++ b/src/uscxml/interpreter/InterpreterDraft6.h @@ -55,6 +55,7 @@ protected: 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 }; } diff --git a/src/uscxml/messages/Data.h b/src/uscxml/messages/Data.h index 584bf09..0d27548 100644 --- a/src/uscxml/messages/Data.h +++ b/src/uscxml/messages/Data.h @@ -44,7 +44,7 @@ public: Data() : type(INTERPRETED) {} // TODO: default INTERPRETED is unfortunate - Data(const std::string& atom, Type type = INTERPRETED) : atom(atom), type(type) {} + Data(const std::string& atom, Type type) : atom(atom), type(type) {} Data(const char* data, size_t size, const std::string& mimeType, bool adopt = false); // convenience constructors @@ -57,9 +57,9 @@ public: Data(double atom) : atom(toStr(atom)), type(INTERPRETED) {} Data(bool atom) : type(INTERPRETED) { if (atom) { - atom = "true"; + this->atom = "true"; } else { - atom = "false"; + this->atom = "false"; } } @@ -67,7 +67,7 @@ public: #if 0 // constructor for arbitrary types, skip if type is subclass though (C++11) - // we will have to drop this constructor as it interferes with operator Data() and entails C++11 + // we will have to drop this constructor as it interferes with operator Data() and requires C++11 template <typename T> Data(T value, typename std::enable_if<! std::is_base_of<Data, T>::value>::type* = nullptr) : atom(toStr(value)), type(INTERPRETED) {} diff --git a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp index 64b61af..6d15f72 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp @@ -474,16 +474,17 @@ void JSCDataModel::setForeach(const std::string& item, // assign array element to item std::stringstream ss; ss << array << "[" << iteration << "]"; - assign(item, ss.str()); + assign(item, Data(ss.str(), Data::INTERPRETED)); if (index.length() > 0) { // assign iteration element to index std::stringstream ss; ss << iteration; - assign(index, ss.str()); + assign(index, Data(ss.str(), Data::INTERPRETED)); } } bool JSCDataModel::isLocation(const std::string& expr) { + // location needs to be RHS and ++ is only valid for RHS JSStringRef scriptJS = JSStringCreateWithUTF8CString((expr + "++").c_str()); JSValueRef exception = NULL; bool valid = JSCheckScriptSyntax(_ctx, scriptJS, NULL, 0, &exception); diff --git a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp index f4bfabd..cd27126 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp @@ -499,12 +499,12 @@ void V8DataModel::setForeach(const std::string& item, // assign array element to item std::stringstream ss; ss << array << "[" << iteration << "]"; - assign(item, ss.str()); + assign(item, Data(ss.str(), Data::INTERPRETED)); if (index.length() > 0) { // assign iteration element to index std::stringstream ss; ss << iteration; - assign(index, ss.str()); + assign(index, Data(ss.str(), Data::INTERPRETED)); } } @@ -753,7 +753,7 @@ void V8DataModel::throwExceptionEvent(const v8::TryCatch& tryCatch) { std::stringstream ssLineNumber; int lineNumber = message->GetLineNumber(); ssLineNumber << lineNumber; - exceptionEvent.data.compound["linenumber"] = Data(ssLineNumber.str()); + exceptionEvent.data.compound["linenumber"] = Data(ssLineNumber.str(), Data::INTERPRETED); int startColumn = message->GetStartColumn(); int endColumn = message->GetEndColumn(); diff --git a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp index 018a8c4..bf8b538 100644 --- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp +++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp @@ -44,14 +44,65 @@ bool pluginConnect(pluma::Host& host) { } #endif -LuaDataModel::LuaDataModel() { - _luaState = NULL; -} - static int luaInspect(lua_State * l) { return 0; } +static luabridge::LuaRef getDataAsLua(lua_State* _luaState, const Data& data) { + luabridge::LuaRef luaData (_luaState); + + if (data.node) { + ERROR_EXECUTION_THROW("No DOM support in Lua datamodel"); + } + if (data.compound.size() > 0) { + luaData = luabridge::newTable(_luaState); + std::map<std::string, Data>::const_iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + luaData[compoundIter->first] = getDataAsLua(_luaState, compoundIter->second); + compoundIter++; + } + luaData["inspect"] = luaInspect; + return luaData; + } + if (data.array.size() > 0) { + luaData = luabridge::newTable(_luaState); + std::list<Data>::const_iterator arrayIter = data.array.begin(); + uint32_t index = 0; + while(arrayIter != data.array.end()) { + luaData[index++] = getDataAsLua(_luaState, *arrayIter); + arrayIter++; + } + luaData["inspect"] = luaInspect; + return luaData; + } + if (data.atom.size() > 0) { + switch (data.type) { + case Data::VERBATIM: { +// luaData = "\"" + data.atom + "\""; + luaData = data.atom; + break; + } + case Data::INTERPRETED: { + if (isNumeric(data.atom.c_str(), 10)) { + if (data.atom.find(".") != std::string::npos) { + luaData = strTo<double>(data.atom); + } else { + luaData = strTo<long>(data.atom); + } + } else { + luaData = data.atom; + } + } + } + return luaData; + } + return luaData; +} + +LuaDataModel::LuaDataModel() { + _luaState = NULL; +} + static int luaInFunction(lua_State * l) { luabridge::LuaRef ref = luabridge::getGlobal(l, "__interpreter"); InterpreterImpl* interpreter = ref.cast<InterpreterImpl*>(); @@ -78,23 +129,22 @@ boost::shared_ptr<DataModelImpl> LuaDataModel::create(InterpreterImpl* interpret luaL_openlibs(dm->_luaState); luabridge::getGlobalNamespace(dm->_luaState).beginClass<InterpreterImpl>("Interpreter").endClass(); + luabridge::setGlobal(dm->_luaState, dm->_interpreter, "__interpreter"); + luabridge::getGlobalNamespace(dm->_luaState).addCFunction("In", luaInFunction); -// luabridge::getGlobalNamespace(dm->_luaState) -// .beginClass <uscxml::Event> ("Event") -// .addProperty("name", &uscxml::Event::getName) -// .addProperty("raw", &uscxml::Event::getRaw) -// .addProperty("data", &uscxml::Event::getData) -// .addProperty("xml", &uscxml::Event::getXML) -// .addProperty("eventType", &uscxml::Event::getEventType) -// .addProperty("origin", &uscxml::Event::getOrigin) -// .addProperty("originType", &uscxml::Event::getOriginType) -// .addProperty("content", &uscxml::Event::getContent) -// .addProperty("invokeId", &uscxml::Event::getInvokeId) -// .addProperty("sendId", &uscxml::Event::getSendId) -// .endClass (); + luabridge::LuaRef ioProcTable = luabridge::newTable(dm->_luaState); - luabridge::setGlobal(dm->_luaState, dm->_interpreter, "__interpreter"); + std::map<std::string, IOProcessor>::const_iterator ioProcIter = dm->_interpreter->getIOProcessors().begin(); + while(ioProcIter != dm->_interpreter->getIOProcessors().end()) { + Data ioProcData = ioProcIter->second.getDataModelVariables(); + ioProcTable[ioProcIter->first] = getDataAsLua(dm->_luaState, ioProcData); + ioProcIter++; + } + luabridge::setGlobal(dm->_luaState, ioProcTable, "_ioprocessors"); + + luabridge::setGlobal(dm->_luaState, dm->_interpreter->getName(), "_name"); + luabridge::setGlobal(dm->_luaState, dm->_interpreter->getSessionId(), "_sessionid"); return dm; } @@ -142,56 +192,6 @@ static Data getLuaAsData(const luabridge::LuaRef& lua) { return data; } -static luabridge::LuaRef getDataAsLua(lua_State* _luaState, const Data& data) { - luabridge::LuaRef luaData (_luaState); - - if (data.node) { - ERROR_EXECUTION_THROW("No DOM support in Lua datamodel"); - } - if (data.compound.size() > 0) { - luaData = luabridge::newTable(_luaState); - std::map<std::string, Data>::const_iterator compoundIter = data.compound.begin(); - while(compoundIter != data.compound.end()) { - luaData[compoundIter->first] = getDataAsLua(_luaState, compoundIter->second); - compoundIter++; - } - luaData["inspect"] = luaInspect; - return luaData; - } - if (data.array.size() > 0) { - luaData = luabridge::newTable(_luaState); - std::list<Data>::const_iterator arrayIter = data.array.begin(); - uint32_t index = 0; - while(arrayIter != data.array.end()) { - luaData[index++] = getDataAsLua(_luaState, *arrayIter); - arrayIter++; - } - luaData["inspect"] = luaInspect; - return luaData; - } - if (data.atom.size() > 0) { - switch (data.type) { - case Data::VERBATIM: { - luaData = "\"" + data.atom + "\""; - break; - } - case Data::INTERPRETED: { - if (isNumeric(data.atom.c_str(), 10)) { - if (data.atom.find(".") != std::string::npos) { - luaData = strTo<double>(data.atom); - } else { - luaData = strTo<long>(data.atom); - } - } else { - luaData = data.atom; - } - } - } - return luaData; - } - return luaData; -} - void LuaDataModel::setEvent(const Event& event) { luabridge::LuaRef luaEvent(_luaState); luaEvent = luabridge::newTable(_luaState); @@ -471,5 +471,16 @@ std::string LuaDataModel::evalAsString(const std::string& expr) { return ""; } +std::string LuaDataModel::andExpressions(std::list<std::string> exprs) { + std::stringstream exprSS; + std::list<std::string>::const_iterator listIter; + std::string andExpr; + for (listIter = exprs.begin(); listIter != exprs.end(); listIter++) { + exprSS << andExpr << *listIter; + andExpr = " && "; + } + return exprSS.str(); +} + }
\ No newline at end of file diff --git a/src/uscxml/plugins/datamodel/lua/LuaDataModel.h b/src/uscxml/plugins/datamodel/lua/LuaDataModel.h index 828db0e..86e7e17 100644 --- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.h +++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.h @@ -85,6 +85,8 @@ public: virtual std::string evalAsString(const std::string& expr); virtual bool evalAsBool(const Arabica::DOM::Node<std::string>& node, const std::string& expr); + virtual std::string andExpressions(std::list<std::string>); + protected: virtual int luaEval(const Arabica::DOM::Element<std::string>& scriptElem, diff --git a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp index c38842b..41e015e 100644 --- a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp +++ b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp @@ -269,7 +269,7 @@ Data XPathDataModel::getStringAsData(const std::string& content) { std::string idx = ss.str(); ss.str(""); ss << ns[i]; - data.compound[idx] = Data(ss.str()); + data.compound[idx] = Data(ss.str(), Data::INTERPRETED); } data.type = Data::INTERPRETED; return data; diff --git a/src/uscxml/plugins/element/file/FileElement.cpp b/src/uscxml/plugins/element/file/FileElement.cpp index d5908ec..247c3c8 100644 --- a/src/uscxml/plugins/element/file/FileElement.cpp +++ b/src/uscxml/plugins/element/file/FileElement.cpp @@ -184,10 +184,10 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) { event.data.compound["file"].compound["name"] = Data(filename, Data::VERBATIM); event.data.compound["file"].compound["path"] = Data(_filepath, Data::VERBATIM); - event.data.compound["file"].compound["mtime"] = toStr(fileStat.st_mtime); - event.data.compound["file"].compound["ctime"] = toStr(fileStat.st_ctime); - event.data.compound["file"].compound["atime"] = toStr(fileStat.st_atime); - event.data.compound["file"].compound["size"] = toStr(fileStat.st_size); + event.data.compound["file"].compound["mtime"] = Data(toStr(fileStat.st_mtime), Data::INTERPRETED); + event.data.compound["file"].compound["ctime"] = Data(toStr(fileStat.st_ctime), Data::INTERPRETED); + event.data.compound["file"].compound["atime"] = Data(toStr(fileStat.st_atime), Data::INTERPRETED); + event.data.compound["file"].compound["size"] = Data(toStr(fileStat.st_size), Data::INTERPRETED); FILE *fp; diff --git a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp index eda4ce8..5e2d8eb 100644 --- a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp +++ b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp @@ -225,7 +225,7 @@ void FFMPEGInvoker::finish(EncodingContext* ctx, const SendRequest& req) { Event event; event.name = "render.done"; - event.data.compound["context"] = context; + event.data.compound["context"] = Data(context, Data::INTERPRETED); event.data.compound["movie"] = Data(movieBuffer, length, "video/mpeg", true); event.data.compound["filename"] = Data(std::string("movie.") + ctx->extension, Data::VERBATIM); diff --git a/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp b/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp index 902b825..c808192 100644 --- a/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp +++ b/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp @@ -84,10 +84,10 @@ Data DirMonInvoker::getDataModelVariables() { std::map<std::string, struct stat> entries = _watcher->getAllEntries(); std::map<std::string, struct stat>::iterator entryIter = entries.begin(); while(entryIter != entries.end()) { - data.compound["file"].compound[entryIter->first].compound["mtime"] = toStr(entryIter->second.st_mtime); - data.compound["file"].compound[entryIter->first].compound["ctime"] = toStr(entryIter->second.st_mtime); - data.compound["file"].compound[entryIter->first].compound["atime"] = toStr(entryIter->second.st_mtime); - data.compound["file"].compound[entryIter->first].compound["size"] = toStr(entryIter->second.st_mtime); + data.compound["file"].compound[entryIter->first].compound["mtime"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED); + data.compound["file"].compound[entryIter->first].compound["ctime"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED); + data.compound["file"].compound[entryIter->first].compound["atime"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED); + data.compound["file"].compound[entryIter->first].compound["size"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED); entryIter++; } @@ -252,10 +252,10 @@ void DirMonInvoker::handleChanges(DirectoryWatch::Action action, const std::stri } if (action != DirectoryWatch::DELETED) { - event.data.compound["file"].compound["mtime"] = toStr(fileStat.st_mtime); - event.data.compound["file"].compound["ctime"] = toStr(fileStat.st_ctime); - event.data.compound["file"].compound["atime"] = toStr(fileStat.st_atime); - event.data.compound["file"].compound["size"] = toStr(fileStat.st_size); + event.data.compound["file"].compound["mtime"] = Data(toStr(fileStat.st_mtime), Data::INTERPRETED); + event.data.compound["file"].compound["ctime"] = Data(toStr(fileStat.st_ctime), Data::INTERPRETED); + event.data.compound["file"].compound["atime"] = Data(toStr(fileStat.st_atime), Data::INTERPRETED); + event.data.compound["file"].compound["size"] = Data(toStr(fileStat.st_size), Data::INTERPRETED); } event.data.compound["file"].compound["name"] = Data(basename, Data::VERBATIM); diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp index 0ebf9b8..2a68be7 100644 --- a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp +++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp @@ -42,7 +42,7 @@ #define EVAL_PARAM_EXPR(param, expr, key) \ if (param.find(key) == param.end() && param.find(expr) != param.end() && _interpreter->getDataModel()) \ - param.insert(std::make_pair(key, _interpreter->getDataModel().evalAsString(param.find(expr)->second.atom))); + param.insert(std::make_pair(key, Data(_interpreter->getDataModel().evalAsString(param.find(expr)->second.atom), Data::INTERPRETED))); #define CAST_PARAM(param, var, key, type) \ if (param.find(key) != param.end()) { \ diff --git a/src/uscxml/plugins/invoker/miles/MilesSessionInvoker.cpp b/src/uscxml/plugins/invoker/miles/MilesSessionInvoker.cpp index 9dfe94f..7b45f2c 100644 --- a/src/uscxml/plugins/invoker/miles/MilesSessionInvoker.cpp +++ b/src/uscxml/plugins/invoker/miles/MilesSessionInvoker.cpp @@ -282,7 +282,7 @@ void MilesSessionInvoker::send(const SendRequest& req) { void MilesSessionInvoker::processEventStart(const std::string& origin, const std::string& userid, const std::string& reflector, const std::string& session) { Event ev; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); //std::cout << req; if(num_connected>0) { num_connected++; @@ -383,7 +383,7 @@ void MilesSessionInvoker::processEventStart(const std::string& origin, const std void MilesSessionInvoker::processEventStop(const std::string& origin) { Event ev; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); if(num_connected==0) { LOG(ERROR) << "not connected"; @@ -419,7 +419,7 @@ void MilesSessionInvoker::processEventStop(const std::string& origin) { void MilesSessionInvoker::processEventParticipants(const std::string& origin) { Event ev; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); if(num_connected==0) { LOG(ERROR) << "not connected"; ev.name = "participants.error"; @@ -440,7 +440,7 @@ void MilesSessionInvoker::processEventParticipants(const std::string& origin) { void MilesSessionInvoker::processEventThumbnail(const std::string& origin, const std::string& userid) { Event ev; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); if(num_connected==0) { LOG(ERROR) << "not connected"; ev.name = "thumbnail.error"; @@ -496,52 +496,52 @@ void MilesSessionInvoker::processEventThumbnail(const std::string& origin, const void MilesSessionInvoker::processEventVideoOn(const std::string& origin, const std::string& userid) { Event ev; ev.name = "videoon.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); returnEvent(ev); } void MilesSessionInvoker::processEventVideoOff(const std::string& origin, const std::string& userid) { Event ev; ev.name = "videooff.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); returnEvent(ev); } void MilesSessionInvoker::processEventAudioOn(const std::string& origin, const std::string& userid) { Event ev; ev.name = "audioon.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); returnEvent(ev); } void MilesSessionInvoker::processEventAudioOff(const std::string& origin, const std::string& userid) { Event ev; ev.name = "audiooff.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); returnEvent(ev); } void MilesSessionInvoker::processEventSendVideo(const std::string& origin, size_t width, size_t height, size_t framerate, const std::string& compression) { Event ev; ev.name = "sendvideo.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); sendvideo_enabled = 1; returnEvent(ev); } void MilesSessionInvoker::processEventSendVideoOff(const std::string& origin) { Event ev; ev.name = "sendvideooff.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); returnEvent(ev); sendvideo_enabled = 0; } void MilesSessionInvoker::processEventSendAudio(const std::string& origin, const std::string& encoding) { Event ev; ev.name = "sendaudio.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); returnEvent(ev); sendaudio_enabled = 1; } void MilesSessionInvoker::processEventSendAudioOff(const std::string& origin) { Event ev; ev.name = "sendaudiooff.reply"; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); returnEvent(ev); sendaudio_enabled = 0; } @@ -552,7 +552,7 @@ void MilesSessionInvoker::processEventPostText(const std::string& origin, const int n, length; Event ev; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); if(num_connected==0) { LOG(ERROR) << "not connected"; ev.name = "posttext.error"; @@ -574,7 +574,7 @@ void MilesSessionInvoker::processEventPostText(const std::string& origin, const void MilesSessionInvoker::processEventGetText(const std::string& origin) { Event ev; - ev.data.compound["origin"] = origin; + ev.data.compound["origin"] = Data(origin, Data::INTERPRETED); if(num_connected==0) { LOG(ERROR) << "not connected"; ev.name = "gettext.error"; diff --git a/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp b/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp index 6ef1bd4..61008ff 100644 --- a/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp +++ b/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp @@ -402,7 +402,7 @@ bool UmundoInvoker::protobufToData(Data& data, const google::protobuf::Message& case google::protobuf::FieldDescriptor::TYPE_DOUBLE: if (fieldDesc->is_repeated()) { for (int j = 0; j < reflect->FieldSize(msg, fieldDesc); j++) { - data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedDouble(msg, fieldDesc, j)))); + data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedDouble(msg, fieldDesc, j)), Data::INTERPRETED)); } } else { data.compound[key].atom = toStr(reflect->GetDouble(msg, fieldDesc)); @@ -423,7 +423,7 @@ bool UmundoInvoker::protobufToData(Data& data, const google::protobuf::Message& case google::protobuf::FieldDescriptor::TYPE_UINT32: if (fieldDesc->is_repeated()) { for (int j = 0; j < reflect->FieldSize(msg, fieldDesc); j++) { - data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedUInt32(msg, fieldDesc, j)))); + data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedUInt32(msg, fieldDesc, j)), Data::INTERPRETED)); } } else { data.compound[key].atom = toStr(reflect->GetUInt32(msg, fieldDesc)); @@ -433,7 +433,7 @@ bool UmundoInvoker::protobufToData(Data& data, const google::protobuf::Message& case google::protobuf::FieldDescriptor::TYPE_UINT64: if (fieldDesc->is_repeated()) { for (int j = 0; j < reflect->FieldSize(msg, fieldDesc); j++) { - data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedUInt64(msg, fieldDesc, j)))); + data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedUInt64(msg, fieldDesc, j)), Data::INTERPRETED)); } } else { data.compound[key].atom = toStr(reflect->GetUInt64(msg, fieldDesc)); @@ -442,7 +442,7 @@ bool UmundoInvoker::protobufToData(Data& data, const google::protobuf::Message& case google::protobuf::FieldDescriptor::TYPE_FLOAT: if (fieldDesc->is_repeated()) { for (int j = 0; j < reflect->FieldSize(msg, fieldDesc); j++) { - data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedFloat(msg, fieldDesc, j)))); + data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedFloat(msg, fieldDesc, j)), Data::INTERPRETED)); } } else { data.compound[key].atom = toStr(reflect->GetFloat(msg, fieldDesc)); @@ -456,7 +456,7 @@ bool UmundoInvoker::protobufToData(Data& data, const google::protobuf::Message& case google::protobuf::FieldDescriptor::TYPE_SFIXED32: if (fieldDesc->is_repeated()) { for (int j = 0; j < reflect->FieldSize(msg, fieldDesc); j++) { - data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedInt32(msg, fieldDesc, j)))); + data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedInt32(msg, fieldDesc, j)), Data::INTERPRETED)); } } else { data.compound[key].atom = toStr(reflect->GetInt32(msg, fieldDesc)); @@ -467,7 +467,7 @@ bool UmundoInvoker::protobufToData(Data& data, const google::protobuf::Message& case google::protobuf::FieldDescriptor::TYPE_SFIXED64: if (fieldDesc->is_repeated()) { for (int j = 0; j < reflect->FieldSize(msg, fieldDesc); j++) { - data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedInt64(msg, fieldDesc, j)))); + data.compound[key].array.push_back(Data(toStr(reflect->GetRepeatedInt64(msg, fieldDesc, j)), Data::INTERPRETED)); } } else { data.compound[key].atom = toStr(reflect->GetInt64(msg, fieldDesc)); diff --git a/src/uscxml/transform/ChartToFSM.cpp b/src/uscxml/transform/ChartToFSM.cpp index cc94434..820e3bc 100644 --- a/src/uscxml/transform/ChartToFSM.cpp +++ b/src/uscxml/transform/ChartToFSM.cpp @@ -62,9 +62,72 @@ Interpreter ChartToFSM::flatten(const Interpreter& other) { return flat; } +uint64_t ChartToFSM::stateMachineComplexity(const Arabica::DOM::Element<std::string>& root) { + Complexity complexity = calculateStateMachineComplexity(root); + uint64_t value = complexity.value; + for (std::list<uint64_t>::const_iterator histIter = complexity.history.begin(); histIter != complexity.history.end(); histIter++) { + value *= *histIter; + } + + return value; +} + +ChartToFSM::Complexity ChartToFSM::calculateStateMachineComplexity(const Arabica::DOM::Element<std::string>& root) { + Complexity complexity; + + bool hasFlatHistory = false; + bool hasDeepHistory = false; + + Arabica::DOM::NodeList<std::string> childElems = root.getChildNodes(); + for (int i = 0; i < childElems.getLength(); i++) { + if (childElems.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + Element<std::string> childElem = Element<std::string>(childElems.item(i)); + if (InterpreterImpl::isHistory(childElem)) { + if (HAS_ATTR(childElem, "type") && ATTR(childElem, "type") == "deep") { + hasDeepHistory = true; + } else { + hasFlatHistory = true; + } + } + } + + if (InterpreterImpl::isCompound(root) || TAGNAME(root) == "scxml") { + // compounds can be in any of the child state -> add + NodeSet<std::string> childs = InterpreterImpl::getChildStates(root); + for (int i = 0; i < childs.size(); i++) { + complexity += calculateStateMachineComplexity(Element<std::string>(childs[i])); + } + if (hasFlatHistory) { + complexity.history.push_back(childs.size()); + } + if (hasDeepHistory) { + complexity.history.push_back(complexity.value); + } + } else if (InterpreterImpl::isParallel(root)) { + // parallels are in all states -> multiply + NodeSet<std::string> childs = InterpreterImpl::getChildStates(root); + complexity.value = 1; + for (int i = 0; i < childs.size(); i++) { + complexity *= calculateStateMachineComplexity(Element<std::string>(childs[i])); + } + if (hasDeepHistory) { + complexity.history.push_back(complexity.value); + } + + } else if (InterpreterImpl::isAtomic(root)) { + return 1; + } + + return complexity; +} + FlatteningInterpreter::FlatteningInterpreter(const Document<std::string>& doc) { + _perfProcessed = 0; + _perfTotal = 0; + _lastTimeStamp = tthread::chrono::system_clock::now(); _currGlobalTransition = NULL; // just copy given doc into _document an create _flatDoc for the FSM @@ -108,6 +171,9 @@ InterpreterState FlatteningInterpreter::interpret() { init(); setupIOProcessors(); + uint64_t complexity = ChartToFSM::stateMachineComplexity(_scxml) + 1; + std::cout << "Approximate Complexity: " << complexity << std::endl; + // initialize the datamodel std::string datamodelName; if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "datamodel")) @@ -179,11 +245,23 @@ InterpreterState FlatteningInterpreter::interpret() { #endif createDocument(); + + NodeSet<std::string> elements = InterpreterImpl::filterChildType(Node_base::ELEMENT_NODE, _scxml, true); + uint64_t nrStates = 0; + for (int i = 0; i < elements.size(); i++) { + Element<std::string> stateElem = Element<std::string>(elements[i]); + if (isState(stateElem) && !HAS_ATTR(stateElem, "transient")) + nrStates++; + } + + std::cout << "Actual Complexity: " << nrStates << std::endl; return _state; } void FlatteningInterpreter::executeContent(const Arabica::DOM::Element<std::string>& content, bool rethrow) { // std::cout << content << std::endl; +// std::cout << TAGNAME(content) << std::endl; + GlobalTransition::Action action; if (false) { @@ -193,8 +271,8 @@ void FlatteningInterpreter::executeContent(const Arabica::DOM::Element<std::stri action.onExit = content; } else if (TAGNAME(content) == "onentry") { action.onExit = content; - } else { - assert(false); + } else { // e.g. global script elements + return; } _currGlobalTransition->actions.push_back(action); } @@ -216,9 +294,8 @@ void FlatteningInterpreter::cancelInvoke(const Arabica::DOM::Node<std::string>& void FlatteningInterpreter::internalDoneSend(const Arabica::DOM::Element<std::string>& state) { Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)state; - -// if (parentIsScxmlState(state)) -// return; + if (parentIsScxmlState(state)) + return; // std::cout << "internalDoneSend: " << state << std::endl; @@ -269,7 +346,26 @@ static bool isSuperset(const GlobalTransition* t1, const GlobalTransition* t2) { return isSuperset; } -static NodeSet<std::string> filterChildEnabled(const NodeSet<std::string>& transitions) { +static bool filterSameState(const NodeSet<std::string>& transitions) { + NodeSet<std::string> filteredTransitions; + for (unsigned int i = 0; i < transitions.size(); i++) { + Node<std::string> t1 = transitions[i]; + Node<std::string> p1 = InterpreterImpl::getParentState(t1); + + for (unsigned int j = 0; j < transitions.size(); j++) { + if (i == j) + continue; + Node<std::string> t2 = transitions[j]; + Node<std::string> p2 = InterpreterImpl::getParentState(t2); + + if (p1 == p2) + return false; + } + } + return true; +} + +static bool filterChildEnabled(const NodeSet<std::string>& transitions) { // drop any transition that is already enabled by a child NodeSet<std::string> filteredTransitions; for (unsigned int i = 0; i < transitions.size(); i++) { @@ -280,24 +376,22 @@ static NodeSet<std::string> filterChildEnabled(const NodeSet<std::string>& trans continue; Node<std::string> t2 = transitions[j]; Node<std::string> p2 = InterpreterImpl::getParentState(t2); -// p2 = p2.getParentNode(); + p2 = p2.getParentNode(); // TODO: think about again! while(p2) { if (p1 == p2) { std::string eventDesc1 = ATTR(t1, "event"); std::string eventDesc2 = ATTR(t2, "event"); if (InterpreterImpl::nameMatch(eventDesc1, eventDesc2)) { -// std::cout << "Dropping " << t1 << std::endl << "for " << t2 << std::endl; - goto SKIP_TRANSITION; + return false; } } p2 = p2.getParentNode(); } } filteredTransitions.push_back(t1); -SKIP_TRANSITION: ; } - return filteredTransitions; + return true; } static std::list<GlobalTransition*> sortTransitions(std::list<GlobalTransition*> list) { @@ -372,6 +466,8 @@ void FlatteningInterpreter::explode() { } _globalConf[globalState->stateId] = globalState; + assert(isLegalConfiguration(configuration)); + // get all transition elements from states in the current configuration NodeSet<std::string> allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", configuration); @@ -448,12 +544,30 @@ void FlatteningInterpreter::explode() { break; NodeSet<std::string> transitions; +// std::cout << globalState->stateId << " [" << nrElements << "]: " << std::endl; for (int i = 1; i <= k; i++) { +// std::cout << stack[i] - 1 << ", "; transitions.push_back(allTransitions[stack[i] - 1]); } +// std::cout << std::endl; + + _perfTotal++; + _perfProcessed++; + + if (tthread::chrono::system_clock::now() - _lastTimeStamp > 1000) { + _lastTimeStamp = tthread::chrono::system_clock::now(); +// std::cout << globalState->stateId << " [" << nrElements << "]: " << std::endl; + std::cout << _perfTotal << " [" << _perfProcessed << "/sec]" << std::endl; + _perfProcessed = 0; + } + + // remove transitions in the same state + if(!filterSameState(transitions)) + continue; // remove those transitions with a child transition - transitions = filterChildEnabled(transitions); + if(!filterChildEnabled(transitions)) + continue; // reduce to conflict-free subset transitions = filterPreempted(transitions); @@ -541,6 +655,13 @@ NEXT_DEPTH: _currGlobalTransition = *transListIter; microstep((*transListIter)->transitions); + if (!isLegalConfiguration(_configuration)) { + std::cout << "invalid configuration from " << globalState->stateId << std::endl; + for (int i = 0; i < (*transListIter)->transitions.size(); i++) { + std::cout << (*transListIter)->transitions[i] << std::endl; + } + assert(false); + } explode(); // reset state for next transition set @@ -906,7 +1027,7 @@ GlobalState::GlobalState(const Arabica::XPath::NodeSet<std::string>& activeState histIter != historyStates.end(); histIter++) { const Arabica::XPath::NodeSet<std::string>& histStates = histIter->second; - idSS << "history-"; + idSS << "history--"; idSS << histIter->first << "-"; for (int i = 0; i < histStates.size(); i++) { idSS << ATTR(histStates[i], "id") << "-"; diff --git a/src/uscxml/transform/ChartToFSM.h b/src/uscxml/transform/ChartToFSM.h index dba8d4d..0808a40 100644 --- a/src/uscxml/transform/ChartToFSM.h +++ b/src/uscxml/transform/ChartToFSM.h @@ -26,7 +26,7 @@ #include <DOM/Node.hpp> #include <XPath/XPath.hpp> #include <ostream> - +#include <list> namespace uscxml { class GlobalState; @@ -137,6 +137,10 @@ protected: GlobalState* _start; GlobalTransition* _currGlobalTransition; + uint64_t _perfProcessed; + uint64_t _perfTotal; + uint64_t _lastTimeStamp; + int maxDepth; int maxOrder; @@ -147,6 +151,32 @@ protected: class USCXML_API ChartToFSM { public: static Interpreter flatten(const Interpreter& other); + static uint64_t stateMachineComplexity(const Arabica::DOM::Element<std::string>& root); + +protected: + class USCXML_API Complexity { + public: + Complexity() : value(0) {} + Complexity(uint64_t value) : value(value) {} + + Complexity& operator+=(const Complexity& rhs) { + value += rhs.value; + history.insert(history.end(), rhs.history.begin(), rhs.history.end()); + return *this; + } + + Complexity& operator*=(const Complexity& rhs) { + value *= rhs.value; + history.insert(history.end(), rhs.history.begin(), rhs.history.end()); + return *this; + } + + uint64_t value; + std::list<uint64_t> history; + }; + + static Complexity calculateStateMachineComplexity(const Arabica::DOM::Element<std::string>& root); + }; } diff --git a/src/uscxml/transform/FSMToCPP.cpp b/src/uscxml/transform/FSMToCPP.cpp new file mode 100644 index 0000000..6bf4535 --- /dev/null +++ b/src/uscxml/transform/FSMToCPP.cpp @@ -0,0 +1,545 @@ +/** + * @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 "uscxml/transform/ChartToFSM.h" +#include "uscxml/transform/FSMToCPP.h" +#include <DOM/io/Stream.hpp> +#include <iostream> +#include "uscxml/UUID.h" +#include <math.h> +#include <boost/algorithm/string.hpp> +#include <glog/logging.h> + +namespace uscxml { + +using namespace Arabica::DOM; +using namespace Arabica::XPath; + +void FSMToCPP::writeProgram(std::ostream& stream, + const Interpreter& interpreter) { + FSMToCPP promelaWriter; + interpreter.getImpl()->copyTo(&promelaWriter); + promelaWriter.writeProgram(stream); +} + +FSMToCPP::FSMToCPP() : _eventTrie(".") { +} + +void FSMToCPP::writeEvents(std::ostream& stream) { + std::list<TrieNode*> eventNames = _eventTrie.getWordsWithPrefix(""); + std::list<TrieNode*>::iterator eventIter = eventNames.begin(); + stream << "// event name identifiers" << std::endl; + while(eventIter != eventNames.end()) { + stream << "#define " << "e" << (*eventIter)->identifier << " " << (*eventIter)->identifier; + stream << " // from \"" << (*eventIter)->value << "\"" << std::endl; + eventIter++; + } +} + +void FSMToCPP::writeStates(std::ostream& stream) { + stream << "// state name identifiers" << std::endl; + for (int i = 0; i < _globalStates.size(); i++) { + stream << "#define " << "s" << i << " " << i; + stream << " // from \"" << ATTR(_globalStates[i], "id") << "\"" << std::endl; + } + +} + +Arabica::XPath::NodeSet<std::string> FSMToCPP::getTransientContent(const Arabica::DOM::Node<std::string>& state) { + Arabica::XPath::NodeSet<std::string> content; + Arabica::DOM::Node<std::string> currState = state; + for (;;) { + if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) + break; + content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "invoke", currState)); + content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onentry", currState)); + content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onexit", currState)); + NodeSet<std::string> transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR(transitions[0], "target")]; + } + + return content; +} + +Node<std::string> FSMToCPP::getUltimateTarget(const Arabica::DOM::Node<std::string>& transition) { + Arabica::DOM::Node<std::string> currState = _states[ATTR(transition, "target")]; + + for (;;) { + if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) + return currState; + NodeSet<std::string> transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR(transitions[0], "target")]; + } +} + +void FSMToCPP::writeInlineComment(std::ostream& stream, const Arabica::DOM::Node<std::string>& node) { + if (node.getNodeType() != Node_base::COMMENT_NODE) + return; + + std::string comment = node.getNodeValue(); + boost::trim(comment); + if (!boost::starts_with(comment, "promela-inline:")) + return; + + std::stringstream ssLine(comment); + std::string line; + std::getline(ssLine, line); // consume first line + while(std::getline(ssLine, line)) { + if (line.length() == 0) + continue; + stream << line; + } +} + +void FSMToCPP::writeExecutableContent(std::ostream& stream, const Arabica::DOM::Node<std::string>& node, int indent) { + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + if (node.getNodeType() == Node_base::COMMENT_NODE) { + std::string comment = node.getNodeValue(); + boost::trim(comment); + std::stringstream inlinePromela; + if (!boost::starts_with(comment, "promela-inline:")) + return; + std::stringstream ssLine(comment); + std::string line; + std::getline(ssLine, line); // consume first line + while(std::getline(ssLine, line)) { + if (line.length() == 0) + continue; + inlinePromela << line << std::endl; + } + stream << padding << "skip;" << std::endl; + stream << beautifyIndentation(inlinePromela.str(), indent) << std::endl; + } + + if (node.getNodeType() != Node_base::ELEMENT_NODE) + return; + + if (false) { + } else if(TAGNAME(node) == "state") { + if (HAS_ATTR(node, "transient") && DOMUtils::attributeIsTrue(ATTR(node, "transient"))) { + Arabica::XPath::NodeSet<std::string> execContent = getTransientContent(node); + for (int i = 0; i < execContent.size(); i++) { + writeExecutableContent(stream, execContent[i], indent); + } + } else { + Arabica::DOM::Node<std::string> child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, child, indent); + child = child.getNextSibling(); + } + } + } else if(TAGNAME(node) == "transition") { + stream << "t" << _transitions[node] << ":" << std::endl; + + stream << padding << "atomic {" << std::endl; + writeExecutableContent(stream, _states[ATTR(node, "target")], indent+1); + stream << padding << " skip;" << std::endl; + + Node<std::string> newState = getUltimateTarget(node); + for (int i = 0; i < _globalStates.size(); i++) { + if (newState != _globalStates[i]) + continue; + stream << padding << " s = s" << i << ";" << std::endl; + } + + stream << padding << "}" << std::endl; + if (isFinal(Element<std::string>(newState))) { + stream << padding << "goto terminate;" << std::endl; + } else { + stream << padding << "goto nextStep;" << std::endl; + } + + } else if(TAGNAME(node) == "onentry" || TAGNAME(node) == "onexit") { + Arabica::DOM::Node<std::string> child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, child, indent); + child = child.getNextSibling(); + } + + } else if(TAGNAME(node) == "script") { + NodeSet<std::string> scriptText = filterChildType(Node_base::TEXT_NODE, node, true); + for (int i = 0; i < scriptText.size(); i++) { + stream << beautifyIndentation(scriptText[i].getNodeValue(), indent) << std::endl; + } + + } else if(TAGNAME(node) == "log") { + // ignore + + } else if(TAGNAME(node) == "foreach") { + if (HAS_ATTR(node, "index")) + stream << padding << ATTR(node, "index") << " = 0;" << std::endl; + stream << padding << "for (" << ATTR(node, "item") << " in " << ATTR(node, "array") << ") {" << std::endl; + Arabica::DOM::Node<std::string> child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, child, indent + 1); + child = child.getNextSibling(); + } + if (HAS_ATTR(node, "index")) + stream << padding << " " << ATTR(node, "index") << "++;" << std::endl; + stream << padding << "}" << std::endl; + + } else if(TAGNAME(node) == "if") { + NodeSet<std::string> condChain; + condChain.push_back(node); + condChain.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "elseif", node)); + condChain.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "else", node)); + + writeIfBlock(stream, condChain, indent); + + } else if(TAGNAME(node) == "raise") { + TrieNode* trieNode = _eventTrie.getNodeWithPrefix(ATTR(node, "event")); + stream << padding << "iQ!e" << trieNode->identifier << ";" << std::endl; + } else if(TAGNAME(node) == "send") { + if (!HAS_ATTR(node, "target")) { + // this is for our external queue + TrieNode* trieNode = _eventTrie.getNodeWithPrefix(ATTR(node, "event")); + stream << padding << "tmpQ!e" << trieNode->identifier << ";" << std::endl; + } + } else if(TAGNAME(node) == "invoke") { + } else if(TAGNAME(node) == "uninvoke") { + stream << padding << ATTR(node, "invokeid") << "EventSourceDone" << "= 1;" << std::endl; + } else { + + std::cerr << "'" << TAGNAME(node) << "'" << std::endl << node << std::endl; + assert(false); + } + +} + +void FSMToCPP::writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet<std::string>& condChain, int indent) { + if (condChain.size() == 0) + return; + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + bool noNext = condChain.size() == 1; + bool nextIsElse = false; + if (condChain.size() > 1) { + if (TAGNAME(condChain[1]) == "else") { + nextIsElse = true; + } + } + + Node<std::string> ifNode = condChain[0]; + + stream << padding << "if" << std::endl; + // we need to nest the elseifs to resolve promela if semantics + stream << padding << ":: (" << ATTR(ifNode, "cond") << ") -> {" << std::endl; + + Arabica::DOM::Node<std::string> child; + if (TAGNAME(ifNode) == "if") { + child = ifNode.getFirstChild(); + } else { + child = ifNode.getNextSibling(); + } + while(child) { + if (child.getNodeType() == Node_base::ELEMENT_NODE) { + if (TAGNAME(child) == "elseif" || TAGNAME(child) == "else") + break; + } + writeExecutableContent(stream, child, indent + 1); + child = child.getNextSibling(); + } + stream << padding << "}" << std::endl; + stream << padding << ":: else -> "; + + if (nextIsElse) { + child = condChain[1].getNextSibling(); + stream << "{" << std::endl; + while(child) { + writeExecutableContent(stream, child, indent + 1); + child = child.getNextSibling(); + } + stream << padding << "}" << std::endl; + + } else if (noNext) { + stream << "skip;" << std::endl; + } else { + stream << "{" << std::endl; + + Arabica::XPath::NodeSet<std::string> cdrCondChain; + for (int i = 1; i < condChain.size(); i++) { + cdrCondChain.push_back(condChain[i]); + } + writeIfBlock(stream, cdrCondChain, indent + 1); + stream << padding << "}" << std::endl; + + } + + stream << padding << "fi;" << std::endl; + +} + +std::string FSMToCPP::beautifyIndentation(const std::string& code, int indent) { + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + // remove topmost indentation from every line and reindent + std::stringstream beautifiedSS; + + std::string initialIndent; + bool gotIndent = false; + bool isFirstLine = true; + std::stringstream ssLine(code); + std::string line; + + while(std::getline(ssLine, line)) { + size_t firstChar = line.find_first_not_of(" \t\r\n"); + if (firstChar != std::string::npos) { + if (!gotIndent) { + initialIndent = line.substr(0, firstChar); + gotIndent = true; + } + beautifiedSS << (isFirstLine ? "" : "\n") << padding << boost::replace_first_copy(line, initialIndent, ""); + isFirstLine = false; + } + } + + return beautifiedSS.str(); +} + +void FSMToCPP::writeDeclarations(std::ostream& stream) { + + // get all data elements + NodeSet<std::string> datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); + NodeSet<std::string> dataText = filterChildType(Node_base::TEXT_NODE, datas, true); + + // write their text content + stream << "// datamodel variables" << std::endl; + for (int i = 0; i < dataText.size(); i++) { + Node<std::string> data = dataText[i]; + stream << beautifyIndentation(data.getNodeValue()) << std::endl; + } + + stream << std::endl; + stream << "// global variables" << std::endl; + stream << "int e; /* current event */" << std::endl; + stream << "int s; /* current state */" << std::endl; + stream << "chan iQ = [100] of {int} /* internal queue */" << std::endl; + stream << "chan eQ = [100] of {int} /* external queue */" << std::endl; + stream << "chan tmpQ = [100] of {int} /* temporary queue for external events in transitions */" << std::endl; + stream << "int tmpQItem;" << std::endl; + + stream << std::endl; + stream << "// event sources" << std::endl; + +} + +void FSMToCPP::writeFSM(std::ostream& stream) { + NodeSet<std::string> transitions; + + stream << "proctype step() {" << std::endl; + // write initial transition + transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _startState); + assert(transitions.size() == 1); + stream << " // transition's executable content" << std::endl; + writeExecutableContent(stream, transitions[0], 1); + + for (int i = 0; i < _globalStates.size(); i++) { + if (_globalStates[i] == _startState) + continue; + NodeSet<std::string> transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); + for (int j = 0; j < transitions.size(); j++) { + writeExecutableContent(stream, transitions[j], 1); + } + } + + stream << std::endl; + stream << "nextStep:" << std::endl; + stream << " // push send events to external queue" << std::endl; + stream << " if" << std::endl; + stream << " :: len(tmpQ) != 0 -> { tmpQ?e; eQ!e }" << std::endl; + stream << " :: else -> skip;" << std::endl; + stream << " fi;" << std::endl << std::endl; + + stream << " /* pop an event */" << std::endl; + stream << " if" << std::endl; + stream << " :: len(iQ) != 0 -> iQ ? e /* from internal queue */" << std::endl; + stream << " :: else -> eQ ? e /* from external queue */" << std::endl; + stream << " fi;" << std::endl; + stream << " /* event dispatching per state */" << std::endl; + stream << " if" << std::endl; + + writeEventDispatching(stream); + + stream << " :: else -> goto nextStep;" << std::endl; + stream << " fi;" << std::endl; + stream << "terminate: skip;" << std::endl; + + + stream << "}" << std::endl; +} + +void FSMToCPP::writeEventDispatching(std::ostream& stream) { + for (int i = 0; i < _globalStates.size(); i++) { + if (_globalStates[i] == _startState) + continue; + + stream << " :: (s == s" << i << ") -> {" << std::endl; + + NodeSet<std::string> transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); + writeDispatchingBlock(stream, transitions, 2); + stream << " goto nextStep;" << std::endl; + stream << " }" << std::endl; + } +} + +void FSMToCPP::writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet<std::string>& transChain, int indent) { + if (transChain.size() == 0) + return; + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + stream << padding << "if" << std::endl; + stream << padding << ":: ((0"; + + Node<std::string> currTrans = transChain[0]; + std::string eventDesc = ATTR(currTrans, "event"); + if (boost::ends_with(eventDesc, "*")) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + if (boost::ends_with(eventDesc, ".")) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + + if (eventDesc.size() == 0) { + stream << " || 1"; + } else { + std::list<TrieNode*> trieNodes = _eventTrie.getWordsWithPrefix(eventDesc); + + std::list<TrieNode*>::iterator trieIter = trieNodes.begin(); + while(trieIter != trieNodes.end()) { + stream << " || e == e" << (*trieIter)->identifier; + trieIter++; + } + } + + stream << ") && "; + stream << (HAS_ATTR(currTrans, "cond") ? ATTR(currTrans, "cond") : "1"); + stream << ") -> goto t" << _transitions[currTrans] << ";" << std::endl; + ; + + stream << padding << ":: else {" << std::endl; + + Arabica::XPath::NodeSet<std::string> cdrTransChain; + for (int i = 1; i < transChain.size(); i++) { + cdrTransChain.push_back(transChain[i]); + } + writeDispatchingBlock(stream, cdrTransChain, indent + 1); + + stream << padding << " goto nextStep;" << std::endl; + stream << padding << "}" << std::endl; + stream << padding << "fi;" << std::endl; +} + + +void FSMToCPP::writeMain(std::ostream& stream) { + stream << std::endl; + stream << "init {" << std::endl; + stream << " run step();" << std::endl; + stream << "}" << std::endl; + +} + +void FSMToCPP::initNodes() { + // get all states + NodeSet<std::string> states = filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml); + for (int i = 0; i < states.size(); i++) { + _states[ATTR(states[i], "id")] = states[i]; + if (HAS_ATTR(states[i], "transient") && DOMUtils::attributeIsTrue(ATTR(states[i], "transient"))) + continue; + _globalStates.push_back(states[i]); + } + _startState = _states[ATTR(_scxml, "initial")]; + + // initialize event trie with all events that might occur + NodeSet<std::string> internalEventNames; + internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "transition", _scxml).asNodeSet()); + internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "raise", _scxml).asNodeSet()); + internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "send", _scxml).asNodeSet()); + + for (int i = 0; i < internalEventNames.size(); i++) { + if (HAS_ATTR(internalEventNames[i], "event")) { + std::string eventNames = ATTR(internalEventNames[i], "event"); + std::list<std::string> events = tokenizeIdRefs(eventNames); + for (std::list<std::string>::iterator eventIter = events.begin(); + eventIter != events.end(); eventIter++) { + std::string eventName = *eventIter; + if (boost::ends_with(eventName, "*")) + eventName = eventName.substr(0, eventName.size() - 1); + if (boost::ends_with(eventName, ".")) + eventName = eventName.substr(0, eventName.size() - 1); + _eventTrie.addWord(eventName); + } + } + } + + // enumerate transitions + NodeSet<std::string> transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + int index = 0; + for (int i = 0; i < transitions.size(); i++) { + _transitions[transitions[i]] = index++; + } +} + +void FSMToCPP::writeProgram(std::ostream& stream) { + + if (!HAS_ATTR(_scxml, "flat") || !DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + LOG(ERROR) << "Given SCXML document was not flattened"; + return; + } + + if (!HAS_ATTR(_scxml, "datamodel") || ATTR(_scxml, "datamodel") != "promela") { + LOG(ERROR) << "Can only convert SCXML documents with \"promela\" datamodel"; + return; + } + + if (HAS_ATTR(_scxml, "binding") && ATTR(_scxml, "binding") != "early") { + LOG(ERROR) << "Can only convert for early data bindings"; + return; + } + + initNodes(); + + writeEvents(stream); + stream << std::endl; + writeStates(stream); + stream << std::endl; + writeDeclarations(stream); + stream << std::endl; + writeFSM(stream); + stream << std::endl; + writeMain(stream); + stream << std::endl; + +} + +}
\ No newline at end of file diff --git a/src/uscxml/transform/FSMToCPP.h b/src/uscxml/transform/FSMToCPP.h new file mode 100644 index 0000000..59231b0 --- /dev/null +++ b/src/uscxml/transform/FSMToCPP.h @@ -0,0 +1,72 @@ +/** + * @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 + */ + +#ifndef FSMTOCPP_H_201672B0 +#define FSMTOCPP_H_201672B0 + +#include "uscxml/interpreter/InterpreterDraft6.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/util/Trie.h" + +#include <DOM/Document.hpp> +#include <DOM/Node.hpp> +#include <XPath/XPath.hpp> +#include <ostream> + +namespace uscxml { + +class USCXML_API FSMToCPP : public InterpreterDraft6 { +public: + static void writeProgram(std::ostream& stream, + const Interpreter& interpreter); + + static std::string beautifyIndentation(const std::string& code, int indent = 0); + +protected: + FSMToCPP(); + void writeProgram(std::ostream& stream); + + void initNodes(); + + void writeEvents(std::ostream& stream); + void writeStates(std::ostream& stream); + void writeDeclarations(std::ostream& stream); + void writeExecutableContent(std::ostream& stream, const Arabica::DOM::Node<std::string>& node, int indent = 0); + void writeInlineComment(std::ostream& stream, const Arabica::DOM::Node<std::string>& node); + void writeFSM(std::ostream& stream); + void writeEventDispatching(std::ostream& stream); + void writeMain(std::ostream& stream); + + void writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet<std::string>& condChain, int indent = 0); + void writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet<std::string>& transChain, int indent = 0); + + Arabica::XPath::NodeSet<std::string> getTransientContent(const Arabica::DOM::Node<std::string>& state); + Arabica::DOM::Node<std::string> getUltimateTarget(const Arabica::DOM::Node<std::string>& transition); + + Trie _eventTrie; + Arabica::XPath::NodeSet<std::string> _globalStates; + Arabica::DOM::Node<std::string> _startState; + std::map<std::string, Arabica::DOM::Node<std::string> > _states; + std::map<Arabica::DOM::Node<std::string>, int> _transitions; + +}; + +} + +#endif /* end of include guard: FSMTOCPP_H_201672B0 */ diff --git a/src/uscxml/transform/FlatStateIdentifier.h b/src/uscxml/transform/FlatStateIdentifier.h new file mode 100644 index 0000000..2ee0443 --- /dev/null +++ b/src/uscxml/transform/FlatStateIdentifier.h @@ -0,0 +1,155 @@ +/** + * @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 + */ + +#ifndef FLATSTATEIDENTIFIER_H_E9534AF9 +#define FLATSTATEIDENTIFIER_H_E9534AF9 + + +#include <sstream> +#include <string> +#include <list> +#include <map> + +namespace uscxml { + +class USCXML_API FlatStateIdentifier { +public: + FlatStateIdentifier(const std::string& identifier) { + std::string parsedName; + // parse unique state identifier + std::stringstream elemNameSS(identifier); + std::string section; + while(std::getline(elemNameSS, section, ';')) { + if (boost::starts_with(section, "active-")) { + std::stringstream stateSS(section.substr(7)); + std::string state; + while(std::getline(stateSS, state, '-')) { + if (state.length() > 0) { + active.push_back(state); + } + } + } else if (boost::starts_with(section, "entered-")) { + std::stringstream stateSS(section.substr(8)); + std::string state; + while(std::getline(stateSS, state, '-')) { + if (state.length() > 0) { + visited.push_back(state); + } + } + } else if (boost::starts_with(section, "history-")) { + std::stringstream stateSS(section.substr(8)); + std::string state; + std::string history; + while(std::getline(stateSS, state, '-')) { + if (state.length() > 0) { + if (history.size() == 0) { + history = state; + } else { + histories[history].push_back(state); + } + } else { + history = ""; + } + } + } + } + } + + std::string activeId() { + std::stringstream activeSS; + activeSS << "active-"; + for (std::list<std::string>::const_iterator activeIter = active.begin(); activeIter != active.end(); activeIter++) { + activeSS << *activeIter << "-"; + } + return activeSS.str(); + } + + std::list<std::string> active; + std::list<std::string> visited; + std::map<std::string, std::list<std::string> > histories; + + static std::string toHTMLLabel(const std::string& identifier, int minRows = 0) { + FlatStateIdentifier flatId(identifier); + + std::list<std::string>::const_iterator listIter; + std::stringstream labelSS; + std::string seperator; + +// labelSS << "<table valign=\"top\" align=\"left\" cellborder=\"0\" border=\"0\" cellspacing=\"0\" cellpadding=\"2\">"; +// labelSS << "<tr>"; +// labelSS << "<td balign=\"left\" align=\"left\" valign=\"top\">"; + + labelSS << "<b>active: </b>"; + labelSS << "{"; + for (listIter = flatId.active.begin(); listIter != flatId.active.end(); listIter++) { + labelSS << seperator << *listIter; + seperator = ", "; + } + labelSS << "}"; + + if (flatId.visited.size() > 0) { + minRows--; + + labelSS << "<br /><b>init: </b>"; + + labelSS << "{"; + seperator = ""; + for (listIter = flatId.visited.begin(); listIter != flatId.visited.end(); listIter++) { + labelSS << seperator << *listIter; + seperator = ", "; + } + labelSS << "}"; + } + +#if 1 + if (flatId.histories.size() > 0) { + minRows--; + + seperator = ""; + std::string histSeperator = "<br /> "; + + labelSS << "<br /><b>history: </b>"; + + std::map<std::string, std::list<std::string> >::const_iterator histIter; + for (histIter = flatId.histories.begin(); histIter != flatId.histories.end(); histIter++) { + labelSS << histSeperator << histIter->first << ": {"; + + for (listIter = histIter->second.begin(); listIter != histIter->second.end(); listIter++) { + labelSS << seperator << *listIter; + seperator = ", "; + } + labelSS << "}"; + seperator = ""; + } + } +#endif +// while(minRows-- > 0) +// labelSS << "<tr><td valign=\"top\"></td></tr>"; // eat up rest of space +// +// labelSS << "</td>"; +// labelSS << "</tr>"; +// labelSS << "</table>"; + return labelSS.str(); + } + +}; + +} + +#endif /* end of include guard: FLATSTATEIDENTIFIER_H_E9534AF9 */ |