From 61439d49e15045bbe9c3cf55b62fc15909dd48e2 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Tue, 15 Jul 2014 12:17:15 +0200 Subject: Improved support for dot output --- CMakeLists.txt | 17 +- CTestConfig.cmake | 2 +- apps/uscxml-browser.cpp | 8 +- apps/uscxml-dot.cpp | 107 +++++ src/bindings/swig/uscxml_ignores.i | 1 + src/uscxml/Interpreter.cpp | 30 +- src/uscxml/Interpreter.h | 5 +- src/uscxml/URL.cpp | 6 + src/uscxml/URL.h | 5 + src/uscxml/debug/SCXMLDotWriter.cpp | 472 +++++++++++++++------ src/uscxml/debug/SCXMLDotWriter.h | 48 ++- src/uscxml/messages/Data.h | 2 +- src/uscxml/messages/Event.h | 22 +- src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp | 333 +++++++++++++-- src/uscxml/plugins/datamodel/lua/LuaDataModel.h | 1 - .../plugins/datamodel/null/NULLDataModel.cpp | 3 - src/uscxml/plugins/datamodel/null/NULLDataModel.h | 1 - test/CMakeLists.txt | 6 + test/src/test-cmdline-parsing.cpp | 24 +- test/src/test-predicates.cpp | 6 +- test/w3c/lua/test152.scxml | 2 +- 21 files changed, 896 insertions(+), 205 deletions(-) create mode 100644 apps/uscxml-dot.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f016f98..cfe179c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,7 +164,7 @@ else () endif() endif() -SET(USCXML_LIBRARY_HOST_URL_PREFIX "http://uscxml.tk.informatik.tu-darmstadt.de/prebuilt" CACHE STRING "The root path of an URL where to look for prebuilt libraries.") +SET(USCXML_LIBRARY_HOST_URL_PREFIX "http://uscxml.mintwerk.de/prebuilt" CACHE STRING "The root path of an URL where to look for prebuilt libraries.") if (CMAKE_CROSSCOMPILING) if (IOS) @@ -306,6 +306,7 @@ else() OPTION(BUILD_TESTS "Build USCXML tests" ON) OPTION(BUILD_TESTS_W3C_ECMA "Create W3C ECMAScript tests" ON) OPTION(BUILD_TESTS_W3C_XPATH "Create W3C XPath tests" ON) + OPTION(BUILD_TESTS_W3C_LUA "Create W3C Lua tests" ON) OPTION(BUILD_TESTS_FSM_ECMA "Create FSM converted W3C ECMAScript tests" OFF) OPTION(BUILD_TESTS_FSM_XPATH "Create FSM converted W3C XPath tests" OFF) endif() @@ -1041,6 +1042,20 @@ if (NOT CMAKE_CROSSCOMPILING) set_target_properties(uscxml-transform PROPERTIES FOLDER "Apps") install_executable(TARGETS uscxml-transform COMPONENT tools) + if (WIN32) + add_executable(uscxml-dot apps/uscxml-dot.cpp ${PROJECT_SOURCE_DIR}/contrib/src/getopt/getopt.c) + else() + add_executable(uscxml-dot apps/uscxml-dot.cpp) + endif() + target_link_libraries(uscxml-dot uscxml) + if (NOT CMAKE_CROSSCOMPILING) + if (ENABLE_COTIRE) + set_target_properties(uscxml-dot PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) + endif() + endif() + set_target_properties(uscxml-dot PROPERTIES FOLDER "Apps") + install_executable(TARGETS uscxml-dot COMPONENT tools) + if (PROTOBUF_FOUND AND OFF) file(GLOB W3C-MMI-COMMON ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.cpp ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.h) diff --git a/CTestConfig.cmake b/CTestConfig.cmake index 7345182..89043b5 100644 --- a/CTestConfig.cmake +++ b/CTestConfig.cmake @@ -9,6 +9,6 @@ set(CTEST_PROJECT_NAME "uscxml") set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") set(CTEST_DROP_METHOD "http") -set(CTEST_DROP_SITE "umundo.tk.informatik.tu-darmstadt.de") +set(CTEST_DROP_SITE "umundo.mintwerk.de") set(CTEST_DROP_LOCATION "/cdash/submit.php?project=uscxml") set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/apps/uscxml-browser.cpp b/apps/uscxml-browser.cpp index 50b3202..e431fdf 100644 --- a/apps/uscxml-browser.cpp +++ b/apps/uscxml-browser.cpp @@ -167,11 +167,10 @@ int main(int argc, char** argv) { // instantiate and configure interpreters std::list interpreters; - std::map::iterator confIter = options.interpreters.begin(); - while(confIter != options.interpreters.end()) { + for(int i = 0; i < options.interpreters.size(); i++) { - InterpreterOptions* currOptions = confIter->second; - std::string documentURL = confIter->first; + InterpreterOptions* currOptions = options.interpreters[0].second; + std::string documentURL = options.interpreters[0].first; LOG(INFO) << "Processing " << documentURL; Interpreter interpreter = Interpreter::fromURI(documentURL); @@ -192,7 +191,6 @@ int main(int argc, char** argv) { } else { LOG(ERROR) << "Cannot create interpreter from " << documentURL; } - confIter++; } // start interpreters diff --git a/apps/uscxml-dot.cpp b/apps/uscxml-dot.cpp new file mode 100644 index 0000000..3324e54 --- /dev/null +++ b/apps/uscxml-dot.cpp @@ -0,0 +1,107 @@ +#include "uscxml/config.h" +#include "uscxml/Interpreter.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/debug/SCXMLDotWriter.h" +#include + +#include "uscxml/Factory.h" +#include + +using namespace uscxml; + +void printUsageAndExit(const char* progName) { + // remove path from program name + std::string progStr(progName); + if (progStr.find_last_of(PATH_SEPERATOR) != std::string::npos) { + progStr = progStr.substr(progStr.find_last_of(PATH_SEPERATOR) + 1, progStr.length() - (progStr.find_last_of(PATH_SEPERATOR) + 1)); + } + + printf("%s version " USCXML_VERSION " (" CMAKE_BUILD_TYPE " build - " CMAKE_COMPILER_STRING ")\n", progStr.c_str()); + printf("Usage\n"); + printf("\t%s", progStr.c_str()); + printf(" [-dN_0] URL"); + printf(" [[-dN_1] state_id1] .. [[-dN_M] state_idM]"); + printf("\n"); + printf("Options\n"); + printf("\tURL : URL of SCXML document\n"); + printf("\t-d : depth below anchor node (INF per default)\n"); + printf("\tstate_id : anchor node state id (topmost scxml element per default)\n"); + printf("\n"); + exit(1); +} + +int currOpt = 1; + +int consumeDepthOption(int argc, char** argv) { + std::string test = argv[currOpt]; + if (boost::starts_with(test, "-")) { + int value = 0; + if (test.size() > 2) { + // no space before value + value = strTo(test.substr(2, test.size() - 2)); + } else { + // space before value + if (argc > currOpt) { + std::string tmp = argv[++currOpt]; + value = strTo(tmp); + } else { + printUsageAndExit(argv[0]); + } + } + currOpt++; + return value; + } + + return -1; +} + +int main(int argc, char** argv) { + + // setup logging + google::LogToStderr(); + google::InitGoogleLogging(argv[0]); + + std::list stateAnchors; + + if (argc < 2) + printUsageAndExit(argv[0]); + + try { + // see if there is an initial depth given for root + int depth = consumeDepthOption(argc, argv); + if (depth >= 0) { + SCXMLDotWriter::StateAnchor anchor; + anchor.depth = depth; + stateAnchors.push_back(anchor); + } + + // current option has to be the interpreter's name + URL inputFile(argv[currOpt++]); + Interpreter interpreter = Interpreter::fromURI(inputFile); + + for (; currOpt < argc; currOpt++) { + SCXMLDotWriter::StateAnchor anchor; + depth = consumeDepthOption(argc, argv); + + if (depth >= 0) { + anchor.depth = depth; + } + + if (argc > currOpt) { + std::string expr(argv[currOpt++]); + anchor.element = interpreter.getImpl()->getState(expr); + } else { + printUsageAndExit(argv[0]); + } + + stateAnchors.push_back(anchor); + } + + SCXMLDotWriter::toDot("machine.dot", interpreter, stateAnchors); + + } catch(Event e) { + std::cerr << e << std::cout; + } + + return EXIT_SUCCESS; +} diff --git a/src/bindings/swig/uscxml_ignores.i b/src/bindings/swig/uscxml_ignores.i index 9a0dbe4..24a6ffa 100644 --- a/src/bindings/swig/uscxml_ignores.i +++ b/src/bindings/swig/uscxml_ignores.i @@ -38,6 +38,7 @@ %ignore uscxml::Interpreter::Interpreter(const boost::shared_ptr); %ignore uscxml::Interpreter::Interpreter(const Interpreter&); %ignore uscxml::Interpreter::getDelayQueue(); +%ignore uscxml::Interpreter::fromURI(const URI&); %ignore uscxml::Interpreter::fromDOM; %ignore uscxml::Interpreter::fromClone; %ignore uscxml::Interpreter::start(); diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index d010a8a..8a9ba63 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -210,8 +210,9 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { goto DONE_PARSING_CMD; std::string url = argv[optind]; - options.interpreters[url] = new InterpreterOptions(); - currOptions = options.interpreters[url]; + + options.interpreters.push_back(std::make_pair(url, new InterpreterOptions())); + currOptions = options.interpreters.back().second; argc -= optind; argv += optind; @@ -369,9 +370,9 @@ Interpreter Interpreter::fromDOM(const Arabica::DOM::Document& dom, tthread::lock_guard lock(_instanceMutex); boost::shared_ptr interpreterImpl = boost::shared_ptr(new INTERPRETER_IMPL); Interpreter interpreter(interpreterImpl); - interpreterImpl->_document = dom; interpreterImpl->setNameSpaceInfo(nameSpaceInfo); interpreterImpl->_document = dom; + interpreterImpl->setupDOM(); // interpreterImpl->init(); _instances[interpreterImpl->getSessionId()] = interpreterImpl; @@ -389,7 +390,12 @@ Interpreter Interpreter::fromXML(const std::string& xml) { } Interpreter Interpreter::fromURI(const std::string& uri) { - URL absUrl(uri); + URL url(uri); + return fromURI(url); +} + +Interpreter Interpreter::fromURI(const URL& uri) { + URL absUrl = uri; if (!absUrl.isAbsolute()) { if (!absUrl.toAbsoluteCwd()) { ERROR_COMMUNICATION_THROW("URL is not absolute or does not have file schema"); @@ -450,6 +456,7 @@ Interpreter Interpreter::fromInputSource(Arabica::SAX::InputSource& if (parser.parse(source) && parser.getDocument() && parser.getDocument().hasChildNodes()) { interpreterImpl->setNameSpaceInfo(parser.nameSpace); interpreterImpl->_document = parser.getDocument(); + interpreterImpl->setupDOM(); } else { if (parser.errorsReported()) { ERROR_PLATFORM_THROW(parser.errors()) @@ -483,7 +490,7 @@ void InterpreterImpl::copyTo(InterpreterImpl* other) { if (parser.parse(inputSource) && parser.getDocument() && parser.getDocument().hasChildNodes()) { other->setNameSpaceInfo(parser.nameSpace); other->_document = parser.getDocument(); - other->init(); + other->setupDOM(); } else { if (parser.errorsReported()) { LOG(ERROR) << parser.errors(); @@ -505,7 +512,7 @@ void InterpreterImpl::copyTo(InterpreterImpl* other) { other->_document = clonedDocument; other->setNameSpaceInfo(_nsInfo); - other->init(); + other->setupDOM(); #endif } } @@ -688,7 +695,7 @@ void InterpreterImpl::reset() { setInterpreterState(USCXML_INSTANTIATED); } -void InterpreterImpl::setupAndNormalizeDOM() { +void InterpreterImpl::setupDOM() { if (_domIsSetup) return; @@ -748,6 +755,7 @@ void InterpreterImpl::setupAndNormalizeDOM() { eventTarget.addEventListener("DOMNodeRemoved", _domEventListener, true); eventTarget.addEventListener("DOMSubtreeModified", _domEventListener, true); + _domIsSetup = true; } void InterpreterImpl::init() { @@ -756,7 +764,7 @@ void InterpreterImpl::init() { _factory = Factory::getInstance(); // setup and normalize DOM - setupAndNormalizeDOM(); + setupDOM(); // get our name or generate as UUID if (_name.length() == 0) @@ -2326,7 +2334,11 @@ bool InterpreterImpl::isInitial(const Arabica::DOM::Element& state) if (!isState(state)) return false; - Arabica::DOM::Element parent = (Element)state.getParentNode(); + Arabica::DOM::Node parentNode = state.getParentNode(); + if (parentNode.getNodeType() != Node_base::ELEMENT_NODE) + return false; + + Arabica::DOM::Element parent = (Element)parentNode; if (!isState(parent)) return true; // scxml element diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 1bdae5c..4659b13 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -132,7 +132,7 @@ public: std::string certificate; std::string privateKey; std::string publicKey; - std::map interpreters; + std::vector > interpreters; std::map additionalParameters; std::string error; @@ -423,7 +423,7 @@ protected: InterpreterImpl(); void init(); - void setupAndNormalizeDOM(); + void setupDOM(); virtual void setupIOProcessors(); void initializeData(const Arabica::DOM::Element& data); @@ -529,6 +529,7 @@ public: const NameSpaceInfo& nameSpaceInfo); static Interpreter fromXML(const std::string& xml); static Interpreter fromURI(const std::string& uri); + static Interpreter fromURI(const URL& uri); static Interpreter fromClone(const Interpreter& other); Interpreter() : _impl() {} // the empty, invalid interpreter diff --git a/src/uscxml/URL.cpp b/src/uscxml/URL.cpp index 08135fd..624f6c1 100644 --- a/src/uscxml/URL.cpp +++ b/src/uscxml/URL.cpp @@ -350,6 +350,12 @@ const std::string URLImpl::getInContent(bool forceReload) { return _rawInContent.str(); } +const std::string URLImpl::file() const { + if (_pathComponents.size() > 0 && !boost::ends_with(path(), "/")) { + return _pathComponents[_pathComponents.size() - 1]; + } +} + const void URLImpl::download(bool blocking) { tthread::lock_guard lock(_mutex); diff --git a/src/uscxml/URL.h b/src/uscxml/URL.h index 01f3e1b..00c2b30 100644 --- a/src/uscxml/URL.h +++ b/src/uscxml/URL.h @@ -80,6 +80,7 @@ public: const std::string path() const { return _uri.path(); } + const std::string file() const; const std::vector pathComponents() const { return _pathComponents; } @@ -259,6 +260,10 @@ public: const std::vector pathComponents() const { return _impl->pathComponents(); } + const std::string file() const { + return _impl->file(); + } + const std::string asString() const { if (_impl) return _impl->asString(); diff --git a/src/uscxml/debug/SCXMLDotWriter.cpp b/src/uscxml/debug/SCXMLDotWriter.cpp index d5471de..4061f41 100644 --- a/src/uscxml/debug/SCXMLDotWriter.cpp +++ b/src/uscxml/debug/SCXMLDotWriter.cpp @@ -32,9 +32,30 @@ SCXMLDotWriter::SCXMLDotWriter() { _indentation = 0; } -SCXMLDotWriter::SCXMLDotWriter(Interpreter interpreter, const Arabica::DOM::Element& transition) { +SCXMLDotWriter::SCXMLDotWriter(Interpreter interpreter, + const std::list& stateAnchors, + const Arabica::DOM::Element& transition) { _interpreter = interpreter; + _xmlNSPrefix = _interpreter.getNameSpaceInfo().xmlNSPrefix; _transition = transition; + _anchors = stateAnchors; + + if (_anchors.size() == 0) { + NodeList scxmlElems = interpreter.getDocument().getElementsByTagName("scxml"); + + StateAnchor anchor; + anchor.element = (Arabica::DOM::Element)scxmlElems.item(0); + _anchors.push_back(anchor); + } + + for (std::list::iterator anchIter = _anchors.begin(); anchIter != _anchors.end(); anchIter++) { + if (!anchIter->element) { + NodeList scxmlElems = interpreter.getDocument().getElementsByTagName("scxml"); + anchIter->element = (Arabica::DOM::Element)scxmlElems.item(0); + } + assembleGraph(anchIter->element, anchIter->depth); + } + _iteration = 0; _indentation = 0; } @@ -61,7 +82,9 @@ void SCXMLDotWriter::beforeMicroStep(Interpreter interpreter) { // toDot(fileSS.str(), interpreter); } -void SCXMLDotWriter::beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition) { +void SCXMLDotWriter::beforeTakingTransition(Interpreter interpreter, + const Arabica::DOM::Element& transition, + bool moreComing) { std::ostringstream fileSS; fileSS << interpreter.getName() << "." << std::setw(6) << std::setfill('0') << _iteration++ << ".dot"; toDot(fileSS.str(), interpreter, transition); @@ -74,162 +97,350 @@ std::string SCXMLDotWriter::getPrefix() { return prefix; } -void SCXMLDotWriter::toDot(const std::string& filename, Interpreter interpreter, const Arabica::DOM::Element& transition) { - std::ofstream outfile(filename.c_str()); - NodeList scxmlElems = interpreter.getDocument().getElementsByTagName("scxml"); - SCXMLDotWriter writer(interpreter, transition); - if (scxmlElems.getLength() > 0) { - writer._indentation++; - outfile << "digraph {" << std::endl; - outfile << "rankdir=TB; fontsize=10;" << std::endl; - writer.writeSCXMLElement(outfile, (Arabica::DOM::Element)scxmlElems.item(0)); - writer._indentation--; - outfile << "}" << std::endl; +void SCXMLDotWriter::writeTo(std::ostream& os) { + os << "digraph {" << std::endl; + os << " rankdir=TB;" << std::endl; + os << " fontsize=10;" << std::endl; + // outfile << " splines=ortho;" << std::endl; + // outfile << " splines=false;" << std::endl; + // outfile << " nodesep=1.0;" << std::endl; + + _indentation++; + for (std::list::iterator anchIter = _anchors.begin(); + anchIter != _anchors.end(); anchIter++) { + writeStateElement(os, _graph[idForNode(anchIter->element)]); } + _indentation--; + + + os << "}" << std::endl; } -void SCXMLDotWriter::writeSCXMLElement(std::ostream& os, const Arabica::DOM::Element& elem) { - writeStateElement(os, elem); +void SCXMLDotWriter::toDot(const std::string& filename, + Interpreter interpreter, + const std::list& stateAnchors, + const Arabica::DOM::Element& transition) { -// std::string elemId = idForNode(elem); -// os << getPrefix() << "subgraph \"cluster" << elemId.substr(1, elemId.length() - 1) << " {" << std::endl; -// _indentation++; -// os << getPrefix() << "label=\"" << nameForNode(elem) << "\"" << std::endl; -// writeStateElement(os, (Arabica::DOM::Element)_interpreter->getInitialState()); -// os << getPrefix() << "} " << std::endl; + std::ofstream outfile(filename.c_str()); + SCXMLDotWriter writer(interpreter, stateAnchors, transition); + writer.writeTo(outfile); } -void SCXMLDotWriter::writeStateElement(std::ostream& os, const Arabica::DOM::Element& elem) { +void SCXMLDotWriter::assembleGraph(const Arabica::DOM::Element& state, uint32_t depth) { + std::string nodeId = idForNode(state); - std::string elemId = idForNode(elem); - NodeList childElems = elem.getChildNodes(); + // been here + if (_graph.find(nodeId) != _graph.end()) + return; + + if (depth == 0) { + _graph[nodeId].isBorder = true; + } + + if (ATTR(state, "id") == "WiFiOff") { + assert(true); + } + + _graph[nodeId].node = state; + + if (depth == 0) + return; + + Arabica::XPath::NodeSet childElems = InterpreterImpl::filterChildType(Arabica::DOM::Node_base::ELEMENT_NODE, state); + for (int i = 0; i < childElems.size(); i++) { + Arabica::DOM::Element childElem(childElems[i]); + + if (iequals(TAGNAME(childElem), "history")) { + _histories[ATTR(childElem, "id")] = ATTR(state, "id") + ":" + ATTR(childElem, "id"); + } + + if (iequals(TAGNAME(childElem), "transition")) { + Arabica::XPath::NodeSet targetStates = _interpreter.getImpl()->getTargetStates(childElem); + for (int j = 0; j < targetStates.size(); j++) { + std::string remoteNodeId = idForNode(targetStates[j]); + _graph[nodeId].targets.insert(std::make_pair(remoteNodeId, childElem)); + + // recurse along the transition targets + assembleGraph((Arabica::DOM::Element)targetStates[j], depth - 1); + } + if (targetStates.size() == 0) + _graph[nodeId].targets.insert(std::make_pair(nodeId, childElem)); + + std::list eventNames; + if (HAS_ATTR(childElem, "event")) + eventNames = InterpreterImpl::tokenizeIdRefs(ATTR(childElem, "event")); + if (eventNames.size() == 0) + _graph[nodeId].events.insert(std::make_pair("", childElem)); + for (std::list::iterator evIter = eventNames.begin(); evIter != eventNames.end(); evIter++) { + _graph[nodeId].events.insert(std::make_pair(*evIter, childElem)); + } + } - if (_knownIds.find(elemId) != _knownIds.end()) + if (InterpreterImpl::isState(Element(childElem))) { + // add to initial states if it is initial + if (_interpreter.getImpl()->isInitial(Element(childElem))) { + _graph[nodeId].initialchilds.insert(idForNode(childElem)); + } else if (_interpreter.getImpl()->isParallel(Element(state))) { + _graph[nodeId].initialchilds.insert(idForNode(childElem)); + } + // in any case, it is a child state + _graph[nodeId].childs.insert(idForNode(childElem)); + + // recurse + assembleGraph(childElem, depth - 1); + } + + + } +} + +void SCXMLDotWriter::writeStateElement(std::ostream& os, const DotState& state) { + const Arabica::DOM::Element& stateElem = state.node; + std::string stateId = idForNode(stateElem); + + if (_knownIds.find(stateId) != _knownIds.end()) return; - _knownIds.insert(elemId); + _knownIds.insert(stateId); - bool subgraph = InterpreterImpl::isCompound(elem) || InterpreterImpl::isParallel(elem); + bool subgraph = InterpreterImpl::isCompound(stateElem) || InterpreterImpl::isParallel(stateElem); if (subgraph) { _indentation++; - os << getPrefix() << "subgraph \"cluster_" << elemId << "\" {" << std::endl; - os << getPrefix() << "label=\"" << nameForNode(elem) << "\\l\"" << std::endl; - } + os << std::endl; + os << getPrefix() << "subgraph \"cluster_" << stateId << "\" {" << std::endl; + _indentation++; + os << getPrefix() << "fontsize=14" << std::endl; + os << getPrefix() << "label=<"; + if (InterpreterImpl::isCompound(stateElem)) { + os << "Compound: "; + } else { + os << "Parallel: "; + } + os << nameForNode(stateElem) << ">" << std::endl; +// os << getPrefix() << "rank=\"same\"" << std::endl; + os << getPrefix() << "labeljust=l" << std::endl; +// os << getPrefix() << "ranksep=\"equally\"" << std::endl; - os << getPrefix() << "\"" << elemId << "\"["; - os << "fontsize=10,"; - os << "label=<State
" << nameForNode(elem) << ">,"; + } - // is the state initial? - if (_interpreter.getImpl()->isInitial(elem)) - os << "style=filled, fillcolor=lightgrey, "; + os << std::endl; + os << getPrefix() << "\"" << stateId << "\" [" << std::endl; + _indentation++; - // is this state final? - if (_interpreter.getImpl()->isFinal(elem)) - os << "shape=doublecircle,"; + os << getPrefix() << "fontsize=10," << std::endl; + os << getPrefix() << "shape=plaintext," << std::endl; // is the current state in the basic configuration? - if (InterpreterImpl::isMember(elem, _interpreter.getBasicConfiguration())) - os << "color=red, penwidth=3,"; - - // is the current state a target state? -#if 0 - for (int i = 0; i < _transitions.size(); i++) { - if (InterpreterImpl::isMember(elem, _interpreter.getTargetStates(_transitions[i]))) { - os << "color=red, penwidth=3,"; - break; - } - } -#endif + if (InterpreterImpl::isMember(stateElem, _interpreter.getBasicConfiguration())) + os << getPrefix() << "color=red, penwidth=3," << std::endl; - os << "];" << std::endl; + // is the current state in the basic configuration? + if (state.isBorder) + os << getPrefix() << "color=blue," << std::endl; - std::string details = getDetailedLabel(elem); -// std::cout << details << std::endl; + // is this state final? + if (_interpreter.getImpl()->isFinal(stateElem)) { + 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; + _indentation--; + os << getPrefix() << "];" << std::endl; + return; + } - if (details.size() > 0) { - os << getPrefix() << "\"" << elemId << "Exec\"["; - os << "fontsize=8,"; - os << "shape=box,"; - os << "color=grey,"; - os << "label=<" << details << ">"; - os << "]" << std::endl; - os << getPrefix() << "\"" << elemId << "\" -> \"" << elemId << "Exec\" [arrowhead=none, color=grey]" << std::endl; + // is the state initial? + bool isInitial = _interpreter.getImpl()->isInitial(stateElem); +// if (isInitial) +// os << getPrefix() << "style=filled, fillcolor=lightgrey, " << std::endl; + + DotState::mmap_s_e_t::const_iterator destIterF, destIterB; + std::list outPorts; // count unique keys +#if PER_EVENT_TRANS + // count unique event names + for(DotState::mmap_s_e_t::const_iterator it = state.events.begin(), end = state.events.end(); + it != end; + it = state.events.upper_bound(it->first)) { + outPorts.push_back(it->first); + } +#else + // count unique adjecent nodes + for(DotState::mmap_s_e_t::const_iterator it = state.targets.begin(), end = state.targets.end(); + it != end; + it = state.targets.upper_bound(it->first)) { + outPorts.push_back(it->first); } +#endif -// NodeList childElems = elem.getChildNodes(); -// for (int i = 0; i < childElems.getLength(); i++) { -// if (Interpreter::isState(childElems.item(i))) { -// writeStateElement(os, (Arabica::DOM::Element)childElems.item(i)); -// } -// } + os << getPrefix() << "label = < " << std::endl; - for (int i = 0; i < childElems.getLength(); i++) { - if (childElems.item(i).getNodeType() == Node_base::ELEMENT_NODE && iequals(TAGNAME(childElems.item(i)), "transition")) { - writeTransitionElement(os, (Arabica::DOM::Element)childElems.item(i)); - bool active = (childElems.item(i) == _transition); - os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(childElems.item(i)) << "\" [arrowhead=none" << std::endl; - if (active) { - os << ", penwidth=3, color=red]" << std::endl; - } else { - os << "]" << std::endl; - } + /* + + + + + +
step
foo.error.port
bar
baz
+ */ + + std::string details = getDetailedLabel(stateElem); + + os << "" << std::endl; + os << " " << std::endl; + for(std::list::iterator nameIter = outPorts.begin(); nameIter != outPorts.end(); nameIter++) { +#ifdef PER_EVENT_TRANS + os << " " << std::endl; +#else + // gather all events that activate the transition + std::string portName = *nameIter; + +// std::cout << ATTR(stateElem, "id") << std::endl; + + if (ATTR(stateElem, "id") == "ConfirmQuit") { + assert(true); } - if (InterpreterImpl::isState(Element(childElems.item(i)))) { - writeStateElement(os, (Arabica::DOM::Element)childElems.item(i)); + + std::multimap eventConds; // event to condition + std::pair targetKeyRange = state.targets.equal_range(portName); + for (destIterB = targetKeyRange.first; destIterB != targetKeyRange.second; ++destIterB) { + const Arabica::DOM::Element& transElem = destIterB->second; + std::list eventNames = InterpreterImpl::tokenizeIdRefs(ATTR(transElem, "event")); + for (std::list::iterator eventIter = eventNames.begin(); eventIter != eventNames.end(); eventIter++) { + eventConds.insert(std::make_pair(*eventIter, ATTR(transElem, "cond"))); + } + if (eventNames.size() == 0) { + // spontaneous transition + eventConds.insert(std::make_pair("∅", ATTR(transElem, "cond"))); + } } - if (childElems.item(i).getNodeType() == Node_base::ELEMENT_NODE && iequals(TAGNAME(childElems.item(i)), "initial")) { - NodeList grandChildElems = childElems.item(i).getChildNodes(); - for (int j = 0; j < grandChildElems.getLength(); j++) { - if (grandChildElems.item(j).getNodeType() == Node_base::ELEMENT_NODE && iequals(TAGNAME(grandChildElems.item(j)), "transition")) { - writeTransitionElement(os, (Arabica::DOM::Element)grandChildElems.item(j)); - os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(grandChildElems.item(j)) << "\"" << std::endl; - } + + typedef std::multimap::iterator condIter_t; + std::stringstream outPortSS; + outPortSS << "" << portName << "
"; + + 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 condRange = eventConds.equal_range(eventName); + for (iterB = condRange.first; iterB != condRange.second; ++iterB) { + hasCondition = true; } + + outPortSS << opener << seperator << eventName << (hasCondition ? "" : ""); + seperator = ", "; + opener = ""; + closer = "}"; } + outPortSS << closer; + + os << "
" << std::endl; + +#endif } - if (subgraph) { - _indentation--; - os << getPrefix() << "} " << std::endl; + if (details.size() > 0) { + os << " " << std::endl; } -} + Arabica::XPath::NodeSet histories = InterpreterImpl::filterChildElements(_xmlNSPrefix + "history", stateElem); + for (int i = 0; i < histories.size(); i++) { + os << " " << std::endl; + + } -void SCXMLDotWriter::writeTransitionElement(std::ostream& os, const Arabica::DOM::Element& elem) { - std::string elemId = idForNode(elem); + os << "
" << nameForNode(stateElem) << "
" << *nameIter << "
" << outPortSS.str() << "
" << std::endl; + os << details << std::endl; + os << "
history: " << ATTR(histories[i], "id") << "
" << std::endl << getPrefix() << ">" << std::endl; - Arabica::XPath::NodeSet targetStates = _interpreter.getImpl()->getTargetStates(elem); + _indentation--; + os << getPrefix() << "];" << std::endl; - bool active = (elem == _transition); + // always display childs up to the desired depth + for (std::set::iterator childIter = state.childs.begin(); childIter != state.childs.end(); childIter++) { + if (_graph.find(*childIter) != _graph.end()) + writeStateElement(os, _graph[*childIter]); + } - std::string label; - os << getPrefix() << "\"" << elemId << "\"["; - if (active) { - os << "color=red, penwidth=3, "; + if (subgraph) { + _indentation--; + os << getPrefix() << "} " << std::endl; + _indentation--; } - os << "fontsize=10,"; - os << "shape=box,"; - os << "label=<Transition
"; - if (HAS_ATTR(elem, "event")) - os << "event: " << ATTR(elem, "event"); - if (HAS_ATTR(elem, "cond")) - os << "cond: " << dotEscape(ATTR(elem, "cond")); - if (!HAS_ATTR(elem, "cond") && !HAS_ATTR(elem, "event")) - os << "unconditional"; - os << ">"; - os << "]" << std::endl; - - for (int i = 0; i < targetStates.size(); i++) { - os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(targetStates[i]) << "\""; - if (active) { - os << " [penwidth=3, color=red]" << std::endl; + +#if 1 + std::string initialEdgeStyle = "style=\"dashed\", color=\"black\""; + std::string transitionEdgeStyle = "color=black"; + + for (std::set::iterator initIter = state.initialchilds.begin(); initIter != state.initialchilds.end(); initIter++) { + std::string destId = *initIter; + if (_histories.find(destId) != _histories.end()) { + os << getPrefix() << stateId << ":__name -> " << _histories[destId] << " [" << initialEdgeStyle << "]" << std::endl; + } else if(InterpreterImpl::isFinal(_graph[destId].node)) { + os << getPrefix() << stateId << ":__name -> " << destId << ":__name:nw [" << initialEdgeStyle << "]" << std::endl; } else { - os << std::endl; + os << getPrefix() << stateId << ":__name -> " << destId << ":__name:nw [" << initialEdgeStyle << "]" << std::endl; + } + } + +#if PER_EVENT_TRANS + // iterate all events and make connections + DotState::mmap_s_e_t::const_iterator destIterF, destIterB; + for(destIterF = state.events.begin(); destIterF != state.events.end(); destIterF = destIterB) { + std::string eventName = destIterF->first; + + // all these react to the same event + std::pair keyRange = state.events.equal_range(eventName); + for (destIterB = keyRange.first; destIterB != keyRange.second; ++destIterB) { + const Arabica::DOM::Element& transElem = destIterB->second; + Arabica::XPath::NodeSet targetStates = _interpreter.getImpl()->getTargetStates(transElem); + for (int i = 0; i < targetStates.size(); i++) { + std::string destId = idForNode(targetStates[i]); + if (_histories.find(destId) != _histories.end()) { + os << getPrefix() << "" << stateId << ":\"" << portEscape(eventName) << "\" -> " << _histories[destId] << " [" << transitionEdgeStyle << "]" << std::endl; + } else if(InterpreterImpl::isFinal(_graph[destId].node)) { + os << getPrefix() << stateId << ":\"" << portEscape(eventName) << "\" -> " << destId << " [" << transitionEdgeStyle << "]" << std::endl; + } else { + os << getPrefix() << "" << stateId << ":\"" << portEscape(eventName) << "\" -> " << destId << ":__name [" << transitionEdgeStyle << "]" << std::endl; + } + } + } + } +#else + // iterate all *targets* and make connections + for(destIterF = state.targets.begin(); destIterF != state.targets.end(); destIterF = destIterB) { + std::string eventName = destIterF->first; + + // all these react to the same event + std::pair keyRange = state.targets.equal_range(eventName); + std::set > targetSet; + for (destIterB = keyRange.first; destIterB != keyRange.second; ++destIterB) { + const Arabica::DOM::Element& transElem = destIterB->second; + Arabica::XPath::NodeSet targetStates = _interpreter.getImpl()->getTargetStates(transElem); + for (int i = 0; i < targetStates.size(); i++) { + targetSet.insert(Arabica::DOM::Element(targetStates[i])); + } + } + for (std::set >::iterator stateIter = targetSet.begin(); stateIter != targetSet.end(); stateIter++) { + std::string destId = idForNode(*stateIter); + if (_histories.find(destId) != _histories.end()) { + os << getPrefix() << "" << stateId << ":\"" << portEscape(eventName) << "\" -> " << _histories[destId] << " [" << transitionEdgeStyle << "]" << std::endl; + } else if(InterpreterImpl::isFinal(_graph[destId].node)) { + os << getPrefix() << stateId << ":\"" << portEscape(eventName) << "\" -> " << destId << " [" << transitionEdgeStyle << "]" << std::endl; + } else { + os << getPrefix() << "" << stateId << ":\"" << portEscape(eventName) << "\" -> " << destId << ":__name [" << transitionEdgeStyle << "]" << std::endl; + } + writeStateElement(os, _graph[destId]); } - writeStateElement(os, (Arabica::DOM::Element)targetStates[i]); } +#endif + +#endif } @@ -265,6 +476,10 @@ std::string SCXMLDotWriter::getDetailedLabel(const Arabica::DOM::Element"; + if (iequals(TAGNAME(childElems.item(i)), "history")) { + continue; + } + // provide details for special elements here // param --------- @@ -311,6 +526,8 @@ std::string SCXMLDotWriter::getDetailedLabel(const Arabica::DOM::Element 0) { - ssContent << ""; + ssContent << "
"; std::list::iterator contentIter = content.begin(); while(contentIter != content.end()) { @@ -404,6 +627,13 @@ std::string SCXMLDotWriter::getDetailedLabel(const Arabica::DOM::Element& n std::string elemName; if (node.getNodeType() == Node_base::ELEMENT_NODE) { Arabica::DOM::Element elem = (Arabica::DOM::Element)node; + if (InterpreterImpl::isParallel(elem)) + elemName += "Parallel
"; if (elem.hasAttribute("name")) { - elemName = elem.getAttribute("name"); + elemName += elem.getAttribute("name"); } else if (elem.hasAttribute("id")) { - elemName = elem.getAttribute("id"); + elemName += elem.getAttribute("id"); } } if (elemName.size() == 0) diff --git a/src/uscxml/debug/SCXMLDotWriter.h b/src/uscxml/debug/SCXMLDotWriter.h index 4e2d7a8..30d5fcf 100644 --- a/src/uscxml/debug/SCXMLDotWriter.h +++ b/src/uscxml/debug/SCXMLDotWriter.h @@ -26,6 +26,9 @@ #include #include +#undef max +#include + namespace uscxml { class Interpreter; @@ -54,24 +57,53 @@ class Interpreter; class USCXML_API SCXMLDotWriter : public InterpreterMonitor { public: + struct StateAnchor { + StateAnchor() : depth(std::numeric_limits::max()) {} + Arabica::DOM::Element element; + uint32_t depth; + }; + struct ElemDetails { std::string name; std::string details; std::string content; }; + struct DotState { + DotState() : isBorder(false) {} + Arabica::DOM::Element node; + std::multimap > targets; // key is remote node, transition is element + std::multimap > events; // key is event name, value is transitions that react + + bool isBorder; + std::set childs; + std::set initialchilds; + typedef std::multimap > mmap_s_e_t; + }; + SCXMLDotWriter(); ~SCXMLDotWriter(); virtual void onStableConfiguration(Interpreter interpreter); virtual void afterCompletion(Interpreter interpreter); - virtual void beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition); + virtual void beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing); virtual void beforeMicroStep(Interpreter interpreter); static void toDot(const std::string& filename, Interpreter interpreter, + const Arabica::DOM::Element& transition = Arabica::DOM::Element()) { + std::list emptyAnchors = std::list(); + toDot(filename, interpreter, emptyAnchors, transition); + } + + + static void toDot(const std::string& filename, + Interpreter interpreter, + const std::list& stateAnchors, const Arabica::DOM::Element& transition = Arabica::DOM::Element()); + void writeTo(std::ostream& os); + std::string getDetailedLabel(const Arabica::DOM::Element& elem, int indentation = 0); std::string colorForIndent(int indent); @@ -80,23 +112,31 @@ public: std::string getPrefix(); static std::string dotEscape(const std::string& text); + static std::string portEscape(const std::string& text); protected: SCXMLDotWriter(Interpreter interpreter, + const std::list& stateAnchors, const Arabica::DOM::Element& transition); - void writeSCXMLElement(std::ostream& os, const Arabica::DOM::Element& elem); - void writeStateElement(std::ostream& os, const Arabica::DOM::Element& elem); - void writeTransitionElement(std::ostream& os, const Arabica::DOM::Element& elem); + void assembleGraph(const Arabica::DOM::Element& start, uint32_t depth = std::numeric_limits::max()); + void writeStateElement(std::ostream& os, const DotState& elem); + void writeUnknownNode(std::ostream& os, const std::string& targetId); int _iteration; std::set _knownIds; + std::set _unknownNodes; int _indentation; + std::map _graph; + // these are only set in ephemeral instances per monitor call Arabica::DOM::Element _transition; Interpreter _interpreter; + std::string _xmlNSPrefix; + std::list _anchors; + std::map _histories; }; } diff --git a/src/uscxml/messages/Data.h b/src/uscxml/messages/Data.h index 11b46fb..584bf09 100644 --- a/src/uscxml/messages/Data.h +++ b/src/uscxml/messages/Data.h @@ -212,7 +212,7 @@ public: this->array = array; } - std::string getAtom() { + std::string getAtom() const { return atom; } void setAtom(const std::string& atom) { diff --git a/src/uscxml/messages/Event.h b/src/uscxml/messages/Event.h index 6697bb9..1aa66a1 100644 --- a/src/uscxml/messages/Event.h +++ b/src/uscxml/messages/Event.h @@ -99,35 +99,35 @@ public: return !(*this == other); } - std::string getName() { + std::string getName() const { return name; } void setName(const std::string& name) { this->name = name; } - Type getEventType() { + Type getEventType() const { return eventType; } void setEventType(const Type type) { this->eventType = type; } - std::string getOrigin() { + std::string getOrigin() const { return origin; } void setOrigin(const std::string& origin) { this->origin = origin; } - std::string getOriginType() { + std::string getOriginType() const { return origintype; } void setOriginType(const std::string& originType) { this->origintype = originType; } - Arabica::DOM::Node getDOM() { + Arabica::DOM::Node getDOM() const { return dom; } void setDOM(const Arabica::DOM::Node& dom) { @@ -140,42 +140,42 @@ public: // static Arabica::DOM::Node getFirstDOMElement(const Arabica::DOM::Document dom); // static Arabica::DOM::Document getStrippedDOM(const Arabica::DOM::Document dom); - std::string getRaw() { + std::string getRaw() const { return raw; } void setRaw(const std::string& raw) { this->raw = raw; } - std::string getContent() { + std::string getContent() const { return content; } void setContent(const std::string& content) { this->content = content; } - std::string getXML() { + std::string getXML() const { return xml; } void setXML(const std::string& xml) { this->xml = xml; } - std::string getSendId() { + std::string getSendId() const { return sendid; } void setSendId(const std::string& sendId) { this->sendid = sendId; } - std::string getInvokeId() { + std::string getInvokeId() const { return invokeid; } void setInvokeId(const std::string& invokeId) { this->invokeid = invokeId; } - Data getData() { + Data getData() const { return data; } void setData(const Data& data) { diff --git a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp index 6fb0369..84833fb 100644 --- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp +++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp @@ -21,7 +21,10 @@ #include "uscxml/Common.h" #include "LuaDataModel.h" + #include "LuaBridge.h" +//#include "RefCountedPtr.h" + #include "uscxml/DOMUtils.h" #include "uscxml/Message.h" @@ -45,17 +48,6 @@ LuaDataModel::LuaDataModel() { _luaState = NULL; } -static int luaEventData(lua_State * l); -static int luaEventOrigin(lua_State * l); -static int luaEventOriginType(lua_State * l); -static int luaEventRaw(lua_State * l); -static int luaEventXML(lua_State * l); -static int luaEventName(lua_State * l); -static int luaEventContent(lua_State * l); -static int luaEventSendId(lua_State * l); -static int luaEventInvokeId(lua_State * l); -static int luaEventDestructor(lua_State * l); - static int luaInFunction(lua_State * l) { luabridge::LuaRef ref = luabridge::getGlobal(l, "__interpreter"); InterpreterImpl* interpreter = ref.cast(); @@ -81,9 +73,23 @@ boost::shared_ptr LuaDataModel::create(InterpreterImpl* interpret dm->_luaState = luaL_newstate(); luaL_openlibs(dm->_luaState); - luabridge::getGlobalNamespace(dm->_luaState).beginClass("__interpreter").endClass(); + luabridge::getGlobalNamespace(dm->_luaState).beginClass("Interpreter").endClass(); luabridge::getGlobalNamespace(dm->_luaState).addCFunction("In", luaInFunction); +// luabridge::getGlobalNamespace(dm->_luaState) +// .beginClass ("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::setGlobal(dm->_luaState, dm->_interpreter, "__interpreter"); return dm; @@ -103,13 +109,161 @@ void LuaDataModel::popContext() { void LuaDataModel::initialize() { } +static Data getLuaAsData(const luabridge::LuaRef& lua) { + Data data; + if (lua.isFunction()) { + // TODO: this might lead to a stack-overflow + luabridge::LuaRef luaEvald = lua(); + return getLuaAsData(luaEvald); + } else if(lua.isLightUserdata() || lua.isUserdata()) { + // not sure what to do + } else if(lua.isThread()) { + // not sure what to do + } else if(lua.isNil()) { + data.atom = "undefined"; + data.type = Data::INTERPRETED; + } else if(lua.isString()) { + data.atom = lua.tostring(); + data.type = Data::VERBATIM; + } else if(lua.isNumber()) { + data.atom = lua.tostring(); + data.type = Data::INTERPRETED; + } else if(lua.isTable()) { + for (luabridge::Iterator iter (lua); !iter.isNil (); ++iter) { + luabridge::LuaRef luaKey = iter.key(); + luabridge::LuaRef luaVal = *iter; + data.compound[luaKey.tostring()] = getLuaAsData(luaVal); + } + } + 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::const_iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + luaData[compoundIter->first] = getDataAsLua(_luaState, compoundIter->second); + compoundIter++; + } + return luaData; + } + if (data.array.size() > 0) { + luaData = luabridge::newTable(_luaState); + std::list::const_iterator arrayIter = data.array.begin(); + uint32_t index = 0; + while(arrayIter != data.array.end()) { + luaData[index++] = getDataAsLua(_luaState, *arrayIter); + arrayIter++; + } + return luaData; + } + if (data.atom.size() > 0) { + switch (data.type) { + case Data::VERBATIM: { + luaData = "\"" + data.atom + "\""; + break; + } + case Data::INTERPRETED: { + luaData = data.atom; // not sure + } + } + return luaData; + } + return luaData; +} + void LuaDataModel::setEvent(const Event& event) { + luabridge::LuaRef luaEvent(_luaState); + luaEvent = luabridge::newTable(_luaState); + + luaEvent["name"] = event.name; + luaEvent["raw"] = event.raw; + luaEvent["xml"] = event.xml; + luaEvent["origin"] = event.origin; + luaEvent["origintype"] = event.origintype; + luaEvent["content"] = event.content; + luaEvent["invokeId"] = event.invokeid; + luaEvent["sendId"] = event.sendid; + + switch (event.eventType) { + case Event::INTERNAL: + luaEvent["type"] = "internal"; + break; + case Event::EXTERNAL: + luaEvent["type"] = "external"; + break; + case Event::PLATFORM: + luaEvent["type"] = "platform"; + break; + + default: + break; + } + + if (event.dom) { + ERROR_EXECUTION_THROW("No DOM support in Lua datamodel"); + } else if (event.content.length() > 0) { + // _event.data is a string or JSON + Data json = Data::fromJSON(event.content); + if (!json.empty()) { + luaEvent["data"] = getDataAsLua(_luaState, json); + } else { + luaEvent["data"] = InterpreterImpl::spaceNormalize(event.content); + } + } else { + // _event.data is KVP + Event eventCopy(event); + + if (!eventCopy.params.empty()) { + Event::params_t::iterator paramIter = eventCopy.params.begin(); + while(paramIter != eventCopy.params.end()) { + eventCopy.data.compound[paramIter->first] = paramIter->second; + paramIter++; + } + } + if (!eventCopy.namelist.empty()) { + Event::namelist_t::iterator nameListIter = eventCopy.namelist.begin(); + while(nameListIter != eventCopy.namelist.end()) { + eventCopy.data.compound[nameListIter->first] = nameListIter->second; + nameListIter++; + } + } + + if (!eventCopy.data.empty()) { + luabridge::LuaRef luaData = getDataAsLua(_luaState, eventCopy.data); + assert(luaEvent.isTable()); + assert(luaData.isTable()); + luaEvent["data"] = luaData; + } + } + + luabridge::setGlobal(_luaState, luaEvent, "_event"); } Data LuaDataModel::getStringAsData(const std::string& content) { Data data = Data::fromJSON(content); if (data.empty()) { - data = Data(content, Data::VERBATIM); + std::string trimmedExpr = boost::trim_copy(content); + if (!boost::starts_with(trimmedExpr, "return")) { + trimmedExpr = "return(" + trimmedExpr + ");"; + } + + int preStack = lua_gettop(_luaState); + eval(Arabica::DOM::Element(), trimmedExpr); + int postStack = lua_gettop(_luaState); + int retVals = postStack - preStack; + if (retVals == 1) { + data = getLuaAsData(luabridge::LuaRef::fromStack(_luaState, -1)); + } + postStack = lua_gettop(_luaState); + lua_pop(_luaState, postStack - preStack); + } return data; } @@ -123,6 +277,29 @@ bool LuaDataModel::isLocation(const std::string& expr) { } uint32_t LuaDataModel::getLength(const std::string& expr) { + // we need the result of the expression on the lua stack -> has to "return"! + std::string trimmedExpr = boost::trim_copy(expr); + +// luabridge::LuaRef val = luabridge::getGlobal(_luaState, expr.c_str()); +// std::cout << val.tostring() << std::endl; + + if (!boost::starts_with(trimmedExpr, "return")) { + trimmedExpr = "return(#" + trimmedExpr + ")"; + } + + int preStack = lua_gettop(_luaState); + eval(Arabica::DOM::Element(), trimmedExpr); + int postStack = lua_gettop(_luaState); + int retVals = postStack - preStack; + + if (retVals == 1 && lua_isnumber(_luaState, -1)) { + int result = lua_tointeger(_luaState, -1); + lua_pop(_luaState, 1); + return result; + } + + lua_pop(_luaState, retVals); + ERROR_EXECUTION_THROW("'" + expr + "' does not evaluate to an array."); return 0; } @@ -130,35 +307,113 @@ void LuaDataModel::setForeach(const std::string& item, const std::string& array, const std::string& index, uint32_t iteration) { + iteration++; // test153: arrays start at 1 + + const luabridge::LuaRef& arrRef = luabridge::getGlobal(_luaState, array.c_str()); + if (arrRef.isTable()) { + int preStack = lua_gettop(_luaState); + + // trigger syntax error for invalid items + eval(Arabica::DOM::Element(), "return(" + item + ");"); + int postStack = lua_gettop(_luaState); + lua_pop(_luaState, postStack - preStack); + + const luabridge::LuaRef& val = arrRef[iteration]; + luabridge::setGlobal(_luaState, val, item.c_str()); + +// luabridge::LuaRef retVal = luabridge::getGlobal(_luaState, item.c_str()); +// std::cout << retVal.tostring() << std::endl; + + if (index.length() > 0) { + // assign iteration element to index + luabridge::setGlobal(_luaState, iteration, index.c_str()); + } + } } void LuaDataModel::eval(const Arabica::DOM::Element& scriptElem, const std::string& expr) { - + int error = luaL_loadstring(_luaState, expr.c_str()) || lua_pcall(_luaState, 0, LUA_MULTRET, 0); + if (error) { + std::string errMsg = lua_tostring(_luaState, -1); + lua_pop(_luaState, 1); /* pop error message from the stack */ + ERROR_EXECUTION_THROW(errMsg); + } } bool LuaDataModel::isDeclared(const std::string& expr) { + // see: http://lua-users.org/wiki/DetectingUndefinedVariables return true; } void LuaDataModel::assign(const Arabica::DOM::Element& assignElem, const Arabica::DOM::Node& node, const std::string& content) { + std::string key; + if (HAS_ATTR(assignElem, "id")) { + key = ATTR(assignElem, "id"); + } else if (HAS_ATTR(assignElem, "location")) { + key = ATTR(assignElem, "location"); + } + if (key.length() == 0) { + ERROR_EXECUTION_THROW("Assign element has neither id nor location"); + } + + // flags on attribute are ignored? + if (key.compare("_sessionid") == 0) // test 322 + ERROR_EXECUTION_THROW("Cannot assign to _sessionId"); + if (key.compare("_name") == 0) + ERROR_EXECUTION_THROW("Cannot assign to _name"); + if (key.compare("_ioprocessors") == 0) // test 326 + ERROR_EXECUTION_THROW("Cannot assign to _ioprocessors"); + if (key.compare("_invokers") == 0) + ERROR_EXECUTION_THROW("Cannot assign to _invokers"); + if (key.compare("_event") == 0) + ERROR_EXECUTION_THROW("Cannot assign to _event"); + +// lua_pushnil(_luaState); +// lua_setglobal(_luaState, key.c_str()); + +// luabridge::setGlobal(_luaState, luabridge::Nil(), key.c_str()); +// luabridge::LuaRef val = luabridge::getGlobal(_luaState, key.c_str()); +// std::cout << val.tostring() << std::endl; + + int preStack = lua_gettop(_luaState); + + if (HAS_ATTR(assignElem, "expr")) { + eval(Arabica::DOM::Element(), key + " = " + ATTR(assignElem, "expr") + ";"); + } else if (node) { + ERROR_EXECUTION_THROW("Cannot assign xml nodes in lua datamodel"); + } else if (content.size() > 0) { + try { + eval(Arabica::DOM::Element(), key + " = " + content + ";"); + } catch (...) { + eval(Arabica::DOM::Element(), key + " = " + "\"" + InterpreterImpl::spaceNormalize(content) + "\";"); + } + } else { + eval(Arabica::DOM::Element(), key + " = " + "nil;"); + } + +// val = luabridge::getGlobal(_luaState, key.c_str()); +// std::cout << val.tostring() << std::endl; + + int postStack = lua_gettop(_luaState); + int retVals = postStack - preStack; } void LuaDataModel::assign(const std::string& location, const Data& data) { - + std::cout << "assign" << std::endl; } void LuaDataModel::init(const Arabica::DOM::Element& dataElem, const Arabica::DOM::Node& node, const std::string& content) { - + assign(dataElem, node, content); } void LuaDataModel::init(const std::string& location, const Data& data) { - + std::cout << "init" << std::endl; } /** @@ -171,29 +426,43 @@ bool LuaDataModel::evalAsBool(const Arabica::DOM::Node& node, const // we need the result of the expression on the lua stack -> has to "return"! std::string trimmedExpr = boost::trim_copy(expr); if (!boost::starts_with(trimmedExpr, "return")) { - trimmedExpr = "return(" + trimmedExpr + ")"; + trimmedExpr = "return(" + trimmedExpr + ");"; } - int error = luaL_loadstring(_luaState, trimmedExpr.c_str()) || lua_pcall(_luaState, 0, LUA_MULTRET, 0); - if (error) { - std::string errMsg = lua_tostring(_luaState, -1); - lua_pop(_luaState, 1); /* pop error message from the stack */ - ERROR_EXECUTION_THROW(errMsg); + + int preStack = lua_gettop(_luaState); + eval(Arabica::DOM::Element(), trimmedExpr); + int postStack = lua_gettop(_luaState); + int retVals = postStack - preStack; + + if (retVals == 1 && lua_isboolean(_luaState, -1)) { + bool result = lua_toboolean(_luaState, -1); + lua_pop(_luaState, 1); + return result; } - int stackSize = lua_gettop(_luaState); - if (stackSize != 1) - return false; - if (lua_isboolean(_luaState, -1)) - return lua_toboolean(_luaState, -1); + lua_pop(_luaState, retVals); return false; } std::string LuaDataModel::evalAsString(const std::string& expr) { - return expr; -} + std::string trimmedExpr = boost::trim_copy(expr); + if (!boost::starts_with(trimmedExpr, "return")) { + trimmedExpr = "return(" + trimmedExpr + ")"; + } -double LuaDataModel::evalAsNumber(const std::string& expr) { - return 0; + int preStack = lua_gettop(_luaState); + eval(Arabica::DOM::Element(), trimmedExpr); + int postStack = lua_gettop(_luaState); + int retVals = postStack - preStack; + + if (retVals == 1 && lua_isstring(_luaState, -1)) { + std::string result = lua_tostring(_luaState, -1); + lua_pop(_luaState, 1); + return result; + } + lua_pop(_luaState, retVals); + return ""; } + } \ 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 9677307..c5d9e4b 100644 --- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.h +++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.h @@ -84,7 +84,6 @@ public: const std::string& expr); virtual std::string evalAsString(const std::string& expr); virtual bool evalAsBool(const Arabica::DOM::Node& node, const std::string& expr); - virtual double evalAsNumber(const std::string& expr); protected: diff --git a/src/uscxml/plugins/datamodel/null/NULLDataModel.cpp b/src/uscxml/plugins/datamodel/null/NULLDataModel.cpp index 073657a..d86bdb2 100644 --- a/src/uscxml/plugins/datamodel/null/NULLDataModel.cpp +++ b/src/uscxml/plugins/datamodel/null/NULLDataModel.cpp @@ -150,8 +150,5 @@ std::string NULLDataModel::evalAsString(const std::string& expr) { return expr; } -double NULLDataModel::evalAsNumber(const std::string& expr) { - return 0; -} } \ No newline at end of file diff --git a/src/uscxml/plugins/datamodel/null/NULLDataModel.h b/src/uscxml/plugins/datamodel/null/NULLDataModel.h index f9609c4..2870388 100644 --- a/src/uscxml/plugins/datamodel/null/NULLDataModel.h +++ b/src/uscxml/plugins/datamodel/null/NULLDataModel.h @@ -78,7 +78,6 @@ public: const std::string& expr); virtual std::string evalAsString(const std::string& expr); virtual bool evalAsBool(const Arabica::DOM::Node& node, const std::string& expr); - virtual double evalAsNumber(const std::string& expr); protected: diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7a610a6..10c1213 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -188,6 +188,12 @@ foreach( W3C_TEST ${W3C_TESTS} ) add_test("fsm/${TEST_NAME}" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-w3c -f ${W3C_TEST}) set_property(TEST "fsm/${TEST_NAME}" PROPERTY LABELS "fsm/${TEST_NAME}") endif() + + if (BUILD_DM_LUA AND LUA_FOUND AND BUILD_TESTS_W3C_LUA AND TEST_NAME MATCHES "^lua\\/.*") + add_test(${TEST_NAME} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-w3c ${W3C_TEST}) + set_property(TEST ${TEST_NAME} PROPERTY LABELS ${TEST_NAME}) +# set_tests_properties(${TEST_NAME} PROPERTIES FAIL_REGULAR_EXPRESSION "TEST FAILED") + endif() endif() endforeach() diff --git a/test/src/test-cmdline-parsing.cpp b/test/src/test-cmdline-parsing.cpp index 341f24e..6fa0c57 100644 --- a/test/src/test-cmdline-parsing.cpp +++ b/test/src/test-cmdline-parsing.cpp @@ -47,7 +47,7 @@ int main(int argc, char** argv) { assert(options); assert(options.verbose); assert(options.interpreters.size() == 1); - assert(options.interpreters.find("/foo/bar.scxml") != options.interpreters.end()); + assert(options.interpreters.front().first == "/foo/bar.scxml"); } if (true) { @@ -66,12 +66,13 @@ int main(int argc, char** argv) { assert(options); assert(options.httpPort == 80); assert(options.interpreters.size() == 3); - assert(options.interpreters.find("/foo/bar1.scxml") != options.interpreters.end()); - assert(options.interpreters.find("/foo/bar2.scxml") != options.interpreters.end()); - assert(options.interpreters.find("/foo/bar3.scxml") != options.interpreters.end()); - assert(!options.interpreters["/foo/bar1.scxml"]->withHTTP); - assert(options.interpreters["/foo/bar2.scxml"]->withHTTP); - assert(!options.interpreters["/foo/bar3.scxml"]->withHTTP); + assert(options.interpreters[0].first == "/foo/bar1.scxml"); + assert(options.interpreters[1].first == "/foo/bar2.scxml"); + assert(options.interpreters[2].first == "/foo/bar3.scxml"); + + assert(!options.interpreters[0].second->withHTTP); + assert(options.interpreters[1].second->withHTTP); + assert(!options.interpreters[2].second->withHTTP); } if (true) { @@ -88,9 +89,12 @@ int main(int argc, char** argv) { assert(options); assert(options.httpPort == 80); assert(options.interpreters.size() == 1); - assert(options.interpreters.find("/foo/bar1.scxml") != options.interpreters.end()); - assert(options.interpreters["/foo/bar1.scxml"]->additionalParameters.find("vrml-path") != options.interpreters["/foo/bar1.scxml"]->additionalParameters.end()); - assert(options.interpreters["/foo/bar1.scxml"]->additionalParameters.find("tmp-path") != options.interpreters["/foo/bar1.scxml"]->additionalParameters.end()); + assert(options.interpreters[0].first == "/foo/bar1.scxml"); + + assert(options.interpreters[0].second->additionalParameters.find("vrml-path") + != options.interpreters[0].second->additionalParameters.end()); + assert(options.interpreters[0].second->additionalParameters.find("tmp-path") + != options.interpreters[0].second->additionalParameters.end()); } return EXIT_SUCCESS; diff --git a/test/src/test-predicates.cpp b/test/src/test-predicates.cpp index fb4551e..02e0faf 100644 --- a/test/src/test-predicates.cpp +++ b/test/src/test-predicates.cpp @@ -24,17 +24,17 @@ int main(int argc, char** argv) { assert(interpreter); interpreter.getImpl()->init(); - Node atomicState = interpreter.getImpl()->getState("atomic"); + Element atomicState = interpreter.getImpl()->getState("atomic"); assert(InterpreterImpl::isAtomic(atomicState)); assert(!InterpreterImpl::isParallel(atomicState)); assert(!InterpreterImpl::isCompound(atomicState)); - Node compoundState = interpreter.getImpl()->getState("compound"); + Element compoundState = interpreter.getImpl()->getState("compound"); assert(!InterpreterImpl::isAtomic(compoundState)); assert(!InterpreterImpl::isParallel(compoundState)); assert(InterpreterImpl::isCompound(compoundState)); - Node parallelState = interpreter.getImpl()->getState("parallel"); + Element parallelState = interpreter.getImpl()->getState("parallel"); assert(!InterpreterImpl::isAtomic(parallelState)); assert(InterpreterImpl::isParallel(parallelState)); assert(!InterpreterImpl::isCompound(parallelState)); // parallel states are not compound! diff --git a/test/w3c/lua/test152.scxml b/test/w3c/lua/test152.scxml index 1147c4b..71ff703 100644 --- a/test/w3c/lua/test152.scxml +++ b/test/w3c/lua/test152.scxml @@ -23,7 +23,7 @@ not being executed. --> - + -- cgit v0.12