From 59c9ae81b4911c6458cbe8a5ed78554bdcc82861 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Mon, 20 Oct 2014 09:20:16 +0200 Subject: SCXML -> Promela skips intermediate explicit flat SCXML for ridiculous better memory footprint --- CMakeLists.txt | 19 +- apps/uscxml-transform.cpp | 65 +- src/uscxml/CMakeLists.txt | 3 +- src/uscxml/Interpreter.cpp | 56 +- src/uscxml/Interpreter.h | 11 +- src/uscxml/interpreter/InterpreterDraft6.cpp | 2 +- src/uscxml/interpreter/InterpreterRC.cpp | 23 +- src/uscxml/interpreter/InterpreterRC.h | 3 + src/uscxml/transform/ChartToCPP.cpp | 546 +++++++ src/uscxml/transform/ChartToCPP.h | 72 + src/uscxml/transform/ChartToFSM.cpp | 1106 ++++++------- src/uscxml/transform/ChartToFSM.h | 162 +- src/uscxml/transform/ChartToFlatSCXML.cpp | 351 +++++ src/uscxml/transform/ChartToFlatSCXML.h | 52 + src/uscxml/transform/ChartToPromela.cpp | 2146 ++++++++++++++++++++++++++ src/uscxml/transform/ChartToPromela.h | 279 ++++ src/uscxml/transform/FSMToCPP.cpp | 546 ------- src/uscxml/transform/FSMToCPP.h | 72 - src/uscxml/transform/FSMToPromela.cpp | 1712 -------------------- src/uscxml/transform/FSMToPromela.h | 272 ---- src/uscxml/transform/FlatStateIdentifier.h | 7 + src/uscxml/transform/Transformer.cpp | 20 + src/uscxml/transform/Transformer.h | 79 + test/CMakeLists.txt | 3 +- test/src/test-promela-parser.cpp | 18 +- test/src/test-w3c.cpp | 9 +- test/w3c/analyze_promela_tests.pl | 167 ++ test/w3c/graphs/data/pml_states.data | 84 + test/w3c/graphs/promela-states.sh | 10 + test/w3c/graphs/run_gnuplot.sh | 2 + test/w3c/run_promela_test.cmake | 2 +- 31 files changed, 4463 insertions(+), 3436 deletions(-) create mode 100644 src/uscxml/transform/ChartToCPP.cpp create mode 100644 src/uscxml/transform/ChartToCPP.h create mode 100644 src/uscxml/transform/ChartToFlatSCXML.cpp create mode 100644 src/uscxml/transform/ChartToFlatSCXML.h create mode 100644 src/uscxml/transform/ChartToPromela.cpp create mode 100644 src/uscxml/transform/ChartToPromela.h delete mode 100644 src/uscxml/transform/FSMToCPP.cpp delete mode 100644 src/uscxml/transform/FSMToCPP.h delete mode 100644 src/uscxml/transform/FSMToPromela.cpp delete mode 100644 src/uscxml/transform/FSMToPromela.h create mode 100644 src/uscxml/transform/Transformer.cpp create mode 100644 src/uscxml/transform/Transformer.h create mode 100755 test/w3c/analyze_promela_tests.pl create mode 100644 test/w3c/graphs/data/pml_states.data create mode 100755 test/w3c/graphs/promela-states.sh create mode 100755 test/w3c/graphs/run_gnuplot.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 56eb4fd..268185d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -351,7 +351,7 @@ else() OPTION(BUILD_TESTS_W3C_LUA "Create W3C Lua tests" ON) OPTION(BUILD_TESTS_W3C_PROMELA "Create W3C Promela tests" ON) - OPTION(BUILD_TESTS_FSM_ECMA "Create FSM converted W3C ECMAScript tests" OFF) + OPTION(BUILD_TESTS_FSM_ECMA "Create FSM converted W3C ECMAScript tests" ON) OPTION(BUILD_TESTS_FSM_XPATH "Create FSM converted W3C XPath tests" OFF) endif() OPTION(ENABLE_GCOV "Compile with gcov support" OFF) @@ -377,6 +377,7 @@ OPTION(DIST_PREPARE "Put libraries into the lib folder of the source tree" OFF) set(USCXML_CORE_LIBS) set(USCXML_OPT_LIBS) set(USCXML_FILES) +set(USCXML_TRANSFORM_FILES) set(USCXML_INCLUDE_DIRS) # some compiler flags @@ -1082,9 +1083,11 @@ endif() # Binaries and tests ############################################################ +set(ALL_SOURCE_FILES ${USCXML_FILES} ${USCXML_TRANSFORM_FILES}) list(SORT USCXML_FILES) -# we cannot use source groups in sub directories! -foreach( FILE ${USCXML_FILES} ) +list(SORT USCXML_TRANSFORM_FILES) +# we cannot define source groups in sub directories! +foreach( FILE ${ALL_SOURCE_FILES} ) get_filename_component(PATH ${FILE} PATH) if (${PATH} MATCHES ".*datamodel\\/ecmascript.*") @@ -1127,7 +1130,7 @@ foreach( FILE ${USCXML_FILES} ) elseif (${FILE} MATCHES ".*\\/plugins\\/.*") source_group("Interpreter\\plugins" FILES ${FILE}) elseif (${FILE} MATCHES ".*\\/transform\\/.*") - source_group("Interpreter\\transform" FILES ${FILE}) + # source_group("Interpreter\\transform" FILES ${FILE}) elseif (${FILE} MATCHES ".*\\/util\\/.*") source_group("Interpreter\\util" FILES ${FILE}) elseif (${FILE} MATCHES ".*\\/concurrency\\/.*") @@ -1180,6 +1183,12 @@ else() endif() INSTALL_LIBRARY(TARGETS uscxml COMPONENT library) +add_library(uscxml_transform ${USCXML_TRANSFORM_FILES}) +target_link_libraries(uscxml_transform uscxml) +set_target_properties(uscxml_transform PROPERTIES PROJECT_LABEL "transform" FOLDER "uscxml") + +INSTALL_LIBRARY(TARGETS uscxml_transform COMPONENT library) + if (NOT CMAKE_CROSSCOMPILING) if (ENABLE_COTIRE) set_target_properties(uscxml PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/uscxml/pch.h") @@ -1212,7 +1221,7 @@ if (NOT CMAKE_CROSSCOMPILING) else() add_executable(uscxml-transform apps/uscxml-transform.cpp) endif() - target_link_libraries(uscxml-transform uscxml) + target_link_libraries(uscxml-transform uscxml uscxml_transform) if (NOT CMAKE_CROSSCOMPILING) if (ENABLE_COTIRE) set_target_properties(uscxml-transform PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) diff --git a/apps/uscxml-transform.cpp b/apps/uscxml-transform.cpp index 401ba87..44ca034 100644 --- a/apps/uscxml-transform.cpp +++ b/apps/uscxml-transform.cpp @@ -1,7 +1,7 @@ #include "uscxml/config.h" #include "uscxml/Interpreter.h" -#include "uscxml/transform/ChartToFSM.h" -#include "uscxml/transform/FSMToPromela.h" +#include "uscxml/transform/ChartToFlatSCXML.h" +#include "uscxml/transform/ChartToPromela.h" #include "uscxml/DOMUtils.h" #include #include @@ -57,15 +57,15 @@ void printUsageAndExit(const char* progName) { 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(" [-fs] [-v] [-lN]"); + printf(" [-t pml|flat] [-v] [-lN]"); #ifdef BUILD_AS_PLUGINS printf(" [-p pluginPath]"); #endif printf(" [-i URL] [-o FILE]"); printf("\n"); printf("Options\n"); - printf("\t-f : flatten to state-machine\n"); - printf("\t-s : convert to spin/promela program\n"); + printf("\t-t flat : flatten to SCXML state-machine\n"); + printf("\t-t pml : convert to spin/promela program\n"); printf("\t-v : be verbose\n"); printf("\t-lN : Set loglevel to N\n"); printf("\t-i URL : Input file (defaults to STDIN)\n"); @@ -78,8 +78,7 @@ int main(int argc, char** argv) { using namespace uscxml; bool verbose = false; - bool toFlat = false; - bool toPromela = false; + std::string outType; std::string pluginPath; std::string inputFile; std::string outputFile; @@ -97,8 +96,7 @@ int main(int argc, char** argv) { struct option longOptions[] = { {"verbose", no_argument, 0, 'v'}, - {"to-flat", no_argument, 0, 'f'}, - {"to-promela", no_argument, 0, 's'}, + {"type", required_argument, 0, 't'}, {"plugin-path", required_argument, 0, 'p'}, {"input-file", required_argument, 0, 'i'}, {"output-file", required_argument, 0, 'o'}, @@ -110,7 +108,7 @@ int main(int argc, char** argv) { int optionInd = 0; int option; for (;;) { - option = getopt_long_only(argc, argv, "+vfsp:i:o:l:", longOptions, &optionInd); + option = getopt_long_only(argc, argv, "+vp:t:i:o:l:", longOptions, &optionInd); if (option == -1) { break; } @@ -123,11 +121,8 @@ int main(int argc, char** argv) { case 'v': verbose = true; break; - case 'f': - toFlat = true; - break; - case 's': - toPromela = true; + case 't': + outType = optarg; break; case 'p': pluginPath = optarg; @@ -148,9 +143,19 @@ int main(int argc, char** argv) { } } - if (!toPromela && !toFlat) { - printUsageAndExit(argv[0]); + if (outType.length() == 0 && outputFile.length() > 0) { + // try to get type from outfile extension + size_t dotPos = outputFile.find_last_of("."); + if (dotPos != std::string::npos) { + outType= outputFile.substr(dotPos + 1); + } } + + if (outType.length() == 0) + printUsageAndExit(argv[0]); + + if (outType != "flat" && outType != "scxml" && outType != "pml") + printUsageAndExit(argv[0]); // register plugins if (pluginPath.length() > 0) { @@ -178,27 +183,37 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } - if (toPromela) { - Interpreter flatInterpreter = ChartToFSM::flatten(interpreter); - + if (outType == "pml") { if (outputFile.size() == 0 || outputFile == "-") { - FSMToPromela::writeProgram(std::cout, flatInterpreter); + ChartToPromela::transform(interpreter).writeTo(std::cout); } else { std::ofstream outStream; outStream.open(outputFile.c_str()); - FSMToPromela::writeProgram(outStream, flatInterpreter); + ChartToPromela::transform(interpreter).writeTo(outStream); outStream.close(); } exit(EXIT_SUCCESS); + +// Interpreter flatInterpreter = ChartToPromela::transform(interpreter); +// +// if (outputFile.size() == 0 || outputFile == "-") { +// ChartToPromela::writeProgram(std::cout, flatInterpreter); +// } else { +// std::ofstream outStream; +// outStream.open(outputFile.c_str()); +// ChartToPromela::writeProgram(outStream, flatInterpreter); +// outStream.close(); +// } +// exit(EXIT_SUCCESS); } - if (toFlat) { + if (outType == "scxml" || outType == "flat") { if (outputFile.size() == 0 || outputFile == "-") { - std::cout << ChartToFSM::flatten(interpreter).getDocument(); + ChartToFlatSCXML::transform(interpreter).writeTo(std::cout); } else { std::ofstream outStream; outStream.open(outputFile.c_str()); - outStream << ChartToFSM::flatten(interpreter).getDocument(); + ChartToFlatSCXML::transform(interpreter).writeTo(outStream); outStream.close(); } exit(EXIT_SUCCESS); diff --git a/src/uscxml/CMakeLists.txt b/src/uscxml/CMakeLists.txt index 979f8fd..8e27980 100644 --- a/src/uscxml/CMakeLists.txt +++ b/src/uscxml/CMakeLists.txt @@ -45,7 +45,7 @@ if (NOT BUILD_MINIMAL) transform/*.h ) source_group("Interpreter" FILES ${USCXML_TRANSFORM}) - list (APPEND USCXML_FILES ${USCXML_TRANSFORM}) + list (APPEND USCXML_TRANSFORM_FILES ${USCXML_TRANSFORM}) endif() file(GLOB_RECURSE USCXML_INTERPRETERS @@ -109,4 +109,5 @@ SET(USCXML_LANGUAGE_BINDINGS ${USCXML_LANGUAGE_BINDINGS} PARENT_SCOPE) set(USCXML_INCLUDE_DIRS ${USCXML_INCLUDE_DIRS} PARENT_SCOPE) set(USCXML_OPT_LIBS ${USCXML_OPT_LIBS} PARENT_SCOPE) set(USCXML_FILES ${USCXML_FILES} PARENT_SCOPE) +set(USCXML_TRANSFORM_FILES ${USCXML_TRANSFORM_FILES} PARENT_SCOPE) SET(PLUMA ${PLUMA} PARENT_SCOPE) \ No newline at end of file diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 50917f3..717000e 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -487,56 +487,44 @@ Interpreter Interpreter::fromInputSource(Arabica::SAX::InputSource& } Interpreter Interpreter::fromClone(const Interpreter& other) { - boost::shared_ptr interpreterImpl = boost::shared_ptr(new INTERPRETER_IMPL); + tthread::lock_guard lock(_instanceMutex); - other.getImpl()->copyTo(interpreterImpl); + boost::shared_ptr interpreterImpl = boost::shared_ptr(new INTERPRETER_IMPL); + interpreterImpl->cloneFrom(other.getImpl()); Interpreter interpreter(interpreterImpl); + + _instances[interpreterImpl->getSessionId()] = interpreterImpl; return interpreter; } -void InterpreterImpl::copyTo(InterpreterImpl* other) { - if (getDocument()) { -#if 0 - std::stringstream* ss = new std::stringstream(); - (*ss) << getDocument(); - // we need an auto_ptr for arabica to assume ownership - std::auto_ptr ssPtr(ss); - Arabica::SAX::InputSource inputSource; - inputSource.setByteStream(ssPtr); - - NameSpacingParser parser; - if (parser.parse(inputSource) && parser.getDocument() && parser.getDocument().hasChildNodes()) { - other->setNameSpaceInfo(parser.nameSpace); - other->_document = parser.getDocument(); - other->setupDOM(); - } else { - if (parser.errorsReported()) { - LOG(ERROR) << parser.errors(); - } - } +void InterpreterImpl::writeTo(std::ostream& stream) { + stream << getDocument(); +} -#else - Arabica::DOM::Document clonedDocument; +void InterpreterImpl::cloneFrom(InterpreterImpl* other) { + if (other->getDocument()) { + const Arabica::DOM::Document& otherDoc = other->_document; DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); - clonedDocument = domFactory.createDocument(getDocument().getNamespaceURI(), "", 0); + _document = domFactory.createDocument(otherDoc.getNamespaceURI(), "", 0); - Node child = getDocument().getFirstChild(); + Node child = otherDoc.getFirstChild(); while (child) { - Node newNode = clonedDocument.importNode(child, true); - clonedDocument.appendChild(newNode); + Node newNode = _document.importNode(child, true); + _document.appendChild(newNode); child = child.getNextSibling(); } - other->_document = clonedDocument; + setNameSpaceInfo(other->_nsInfo); + + _baseURI = other->_baseURI; + _sourceURI = other->_sourceURI; - other->setNameSpaceInfo(_nsInfo); - other->setupDOM(); -#endif + setupDOM(); } } -void InterpreterImpl::copyTo(boost::shared_ptr other) { - copyTo(other.get()); +void InterpreterImpl::cloneFrom(boost::shared_ptr other) { + cloneFrom(other.get()); } void InterpreterImpl::setName(const std::string& name) { diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 4b6d26a..6772a59 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -228,9 +228,10 @@ public: virtual ~InterpreterImpl(); - void copyTo(InterpreterImpl* other); - void copyTo(boost::shared_ptr other); - + void cloneFrom(InterpreterImpl* other); + void cloneFrom(boost::shared_ptr other); + virtual void writeTo(std::ostream& stream); + // TODO: We need to move the destructor to the implementations to make these pure virtual virtual InterpreterState interpret(); virtual InterpreterState step(int waitForMS = 0); @@ -601,6 +602,10 @@ public: return *this; } + virtual void writeTo(std::ostream& stream) { + return _impl->writeTo(stream); + } + void reset() { return _impl->reset(); } diff --git a/src/uscxml/interpreter/InterpreterDraft6.cpp b/src/uscxml/interpreter/InterpreterDraft6.cpp index 7dcb768..2bab937 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.cpp +++ b/src/uscxml/interpreter/InterpreterDraft6.cpp @@ -553,7 +553,7 @@ void InterpreterDraft6::handleDOMEvent(Arabica::DOM::Events::Event& if (event.getType().compare("DOMAttrModified") == 0) // we do not care about attributes return; Node target = Arabica::DOM::Node(event.getTarget()); - NodeSet transitions = InterpreterImpl::filterChildElements(_nsInfo.xmlNSPrefix + "transition", target); + NodeSet transitions = InterpreterImpl::filterChildElements(_nsInfo.xmlNSPrefix + "transition", target, true); for (int i = 0; i < transitions.size(); i++) { const Element transElem = Element(transitions[i]); if (_transWithinParallel.find(transElem) != _transWithinParallel.end()) diff --git a/src/uscxml/interpreter/InterpreterRC.cpp b/src/uscxml/interpreter/InterpreterRC.cpp index 8f16cb0..b58a236 100644 --- a/src/uscxml/interpreter/InterpreterRC.cpp +++ b/src/uscxml/interpreter/InterpreterRC.cpp @@ -181,6 +181,7 @@ function computeExitSet(transitions) return statesToExit */ Arabica::XPath::NodeSet InterpreterRC::computeExitSet(const Arabica::XPath::NodeSet& transitions) { + NodeSet statesToExit; for (unsigned int i = 0; i < transitions.size(); i++) { Element t(transitions[i]); @@ -203,13 +204,21 @@ Arabica::XPath::NodeSet InterpreterRC::computeExitSet(const Arabica } std::cout << std::endl; #endif + return statesToExit; } Arabica::XPath::NodeSet InterpreterRC::computeExitSet(const Arabica::DOM::Node& transition) { + if (_exitSet.find(transition) != _exitSet.end()) // speed up removeConflicting + return _exitSet[transition]; + Arabica::XPath::NodeSet transitions; transitions.push_back(transition); - return computeExitSet(transitions); + + Arabica::XPath::NodeSet exitSet = computeExitSet(transitions); + _exitSet[transition] = exitSet; + + return exitSet; } void InterpreterRC::enterStates(const Arabica::XPath::NodeSet& enabledTransitions) { @@ -622,4 +631,16 @@ BREAK_LOOP: return findLCCA(states); } +void InterpreterRC::handleDOMEvent(Arabica::DOM::Events::Event& event) { + InterpreterImpl::handleDOMEvent(event); + + if (event.getType().compare("DOMAttrModified") == 0) // we do not care about attributes + return; + +// Node target = Arabica::DOM::Node(event.getTarget()); +// NodeSet transitions = InterpreterImpl::filterChildElements(_nsInfo.xmlNSPrefix + "transition", target, true); +// if (transitions.size() > 0) + _exitSet.clear(); + +} } \ No newline at end of file diff --git a/src/uscxml/interpreter/InterpreterRC.h b/src/uscxml/interpreter/InterpreterRC.h index 52b45ff..36aca3d 100644 --- a/src/uscxml/interpreter/InterpreterRC.h +++ b/src/uscxml/interpreter/InterpreterRC.h @@ -57,7 +57,10 @@ protected: Arabica::XPath::NodeSet& statesToEnter, Arabica::XPath::NodeSet& statesForDefaultEntry, std::map >& defaultHistoryContent); + virtual void handleDOMEvent(Arabica::DOM::Events::Event& event); +private: + std::map, Arabica::XPath::NodeSet > _exitSet; }; } diff --git a/src/uscxml/transform/ChartToCPP.cpp b/src/uscxml/transform/ChartToCPP.cpp new file mode 100644 index 0000000..6b78374 --- /dev/null +++ b/src/uscxml/transform/ChartToCPP.cpp @@ -0,0 +1,546 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "uscxml/transform/ChartToFSM.h" +#include "uscxml/transform/ChartToCPP.h" +#include +#include +#include "uscxml/UUID.h" +#include +#include +#include + +namespace uscxml { + +using namespace Arabica::DOM; +using namespace Arabica::XPath; + +void ChartToCPP::writeProgram(std::ostream& stream, + const Interpreter& interpreter) { + ChartToCPP promelaWriter; + promelaWriter.cloneFrom(interpreter.getImpl()); + promelaWriter.writeProgram(stream); +} + +ChartToCPP::ChartToCPP() : _eventTrie(".") { +} + +void ChartToCPP::writeEvents(std::ostream& stream) { + std::list eventNames = _eventTrie.getWordsWithPrefix(""); + std::list::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 ChartToCPP::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_CAST(_globalStates[i], "id") << "\"" << std::endl; + } + +} + +Arabica::XPath::NodeSet ChartToCPP::getTransientContent(const Arabica::DOM::Element& state) { + Arabica::XPath::NodeSet content; + Arabica::DOM::Element 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 transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR_CAST(transitions[0], "target")]; + } + + return content; +} + +Node ChartToCPP::getUltimateTarget(const Arabica::DOM::Element& transition) { + Arabica::DOM::Element currState = _states[ATTR(transition, "target")]; + + for (;;) { + if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) + return currState; + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR_CAST(transitions[0], "target")]; + } +} + +void ChartToCPP::writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& 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 ChartToCPP::writeExecutableContent(std::ostream& stream, const Arabica::DOM::Element& 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 execContent = getTransientContent(node); + for (int i = 0; i < execContent.size(); i++) { + writeExecutableContent(stream, Arabica::DOM::Element(execContent[i]), indent); + } + } else { + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, Arabica::DOM::Element(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 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(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 child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, Arabica::DOM::Element(child), indent); + child = child.getNextSibling(); + } + + } else if(TAGNAME(node) == "script") { + NodeSet 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 child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, Arabica::DOM::Element(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 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 ChartToCPP::writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet& 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_CAST(condChain[1]) == "else") { + nextIsElse = true; + } + } + + Node ifNode = condChain[0]; + + stream << padding << "if" << std::endl; + // we need to nest the elseifs to resolve promela if semantics + stream << padding << ":: (" << ATTR_CAST(ifNode, "cond") << ") -> {" << std::endl; + + Arabica::DOM::Node child; + if (TAGNAME_CAST(ifNode) == "if") { + child = ifNode.getFirstChild(); + } else { + child = ifNode.getNextSibling(); + } + while(child) { + if (child.getNodeType() == Node_base::ELEMENT_NODE) { + if (TAGNAME_CAST(child) == "elseif" || TAGNAME_CAST(child) == "else") + break; + } + writeExecutableContent(stream, Arabica::DOM::Element(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, Arabica::DOM::Element(child), indent + 1); + child = child.getNextSibling(); + } + stream << padding << "}" << std::endl; + + } else if (noNext) { + stream << "skip;" << std::endl; + } else { + stream << "{" << std::endl; + + Arabica::XPath::NodeSet 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 ChartToCPP::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 ChartToCPP::writeDeclarations(std::ostream& stream) { + + // get all data elements + NodeSet datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); + NodeSet 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 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 ChartToCPP::writeFSM(std::ostream& stream) { + NodeSet 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, Arabica::DOM::Element(transitions[0]), 1); + + for (int i = 0; i < _globalStates.size(); i++) { + if (_globalStates[i] == _startState) + continue; + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); + for (int j = 0; j < transitions.size(); j++) { + writeExecutableContent(stream, Arabica::DOM::Element(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 ChartToCPP::writeEventDispatching(std::ostream& stream) { + for (int i = 0; i < _globalStates.size(); i++) { + if (_globalStates[i] == _startState) + continue; + + stream << " :: (s == s" << i << ") -> {" << std::endl; + + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); + writeDispatchingBlock(stream, transitions, 2); + stream << " goto nextStep;" << std::endl; + stream << " }" << std::endl; + } +} + +void ChartToCPP::writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& 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 currTrans = transChain[0]; + std::string eventDesc = ATTR_CAST(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 trieNodes = _eventTrie.getWordsWithPrefix(eventDesc); + + std::list::iterator trieIter = trieNodes.begin(); + while(trieIter != trieNodes.end()) { + stream << " || e == e" << (*trieIter)->identifier; + trieIter++; + } + } + + stream << ") && "; + stream << (HAS_ATTR_CAST(currTrans, "cond") ? ATTR_CAST(currTrans, "cond") : "1"); + stream << ") -> goto t" << _transitions[Arabica::DOM::Element(currTrans)] << ";" << std::endl; + ; + + stream << padding << ":: else {" << std::endl; + + Arabica::XPath::NodeSet 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 ChartToCPP::writeMain(std::ostream& stream) { + stream << std::endl; + stream << "init {" << std::endl; + stream << " run step();" << std::endl; + stream << "}" << std::endl; + +} + +void ChartToCPP::initNodes() { + // get all states + NodeSet states = filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml); + for (int i = 0; i < states.size(); i++) { + Arabica::DOM::Element stateElem = Arabica::DOM::Element(states[i]); + _states[ATTR(stateElem, "id")] = stateElem; + if (HAS_ATTR(stateElem, "transient") && DOMUtils::attributeIsTrue(ATTR(stateElem, "transient"))) + continue; + _globalStates.push_back(states[i]); + } + _startState = _states[ATTR(_scxml, "initial")]; + + // initialize event trie with all events that might occur + NodeSet 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_CAST(internalEventNames[i], "event")) { + std::string eventNames = ATTR_CAST(internalEventNames[i], "event"); + std::list events = tokenizeIdRefs(eventNames); + for (std::list::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 transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + int index = 0; + for (int i = 0; i < transitions.size(); i++) { + _transitions[Arabica::DOM::Element(transitions[i])] = index++; + } +} + +void ChartToCPP::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/ChartToCPP.h b/src/uscxml/transform/ChartToCPP.h new file mode 100644 index 0000000..8cdebb9 --- /dev/null +++ b/src/uscxml/transform/ChartToCPP.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 . + * @endcond + */ + +#ifndef FSMTOCPP_H_201672B0 +#define FSMTOCPP_H_201672B0 + +#include "uscxml/interpreter/InterpreterDraft6.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/util/Trie.h" + +#include +#include +#include +#include + +namespace uscxml { + +class USCXML_API ChartToCPP : public InterpreterDraft6 { +public: + static void writeProgram(std::ostream& stream, + const Interpreter& interpreter); + + static std::string beautifyIndentation(const std::string& code, int indent = 0); + +protected: + ChartToCPP(); + 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::Element& node, int indent = 0); + void writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& 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& condChain, int indent = 0); + void writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent = 0); + + Arabica::XPath::NodeSet getTransientContent(const Arabica::DOM::Element& state); + Arabica::DOM::Node getUltimateTarget(const Arabica::DOM::Element& transition); + + Trie _eventTrie; + Arabica::XPath::NodeSet _globalStates; + Arabica::DOM::Element _startState; + std::map > _states; + std::map, int> _transitions; + +}; + +} + +#endif /* end of include guard: FSMTOCPP_H_201672B0 */ diff --git a/src/uscxml/transform/ChartToFSM.cpp b/src/uscxml/transform/ChartToFSM.cpp index 2c5aacd..8eda0a5 100644 --- a/src/uscxml/transform/ChartToFSM.cpp +++ b/src/uscxml/transform/ChartToFSM.cpp @@ -33,6 +33,26 @@ #undef max #include +#define DUMP_STATS(nrTrans) \ +uint64_t now = tthread::chrono::system_clock::now(); \ +if (now - _lastTimeStamp > 1000) { \ + std::cerr << "T: " << _perfTransTotal << " [" << _perfTransProcessed << "/sec]"; \ + if (nrTrans > 0) { \ + std::cerr << " - 2**" << nrTrans << " = " << pow(2.0, static_cast(nrTrans)); \ + } \ + std::cerr << std::endl; \ + std::cerr << "S: " << _globalConf.size() << " [" << _perfStatesProcessed << "/sec]" << std::endl; \ + std::cerr << "C: " << _perfStatesCachedTotal << " [" << _perfStatesCachedProcessed << "/sec]" << std::endl; \ + std::cerr << "X: " << _perfStatesSkippedTotal << " [" << _perfStatesSkippedProcessed << "/sec]" << std::endl; \ + std::cerr << std::endl; \ + _perfTransProcessed = 0; \ + _perfStatesProcessed = 0; \ + _perfStatesCachedProcessed = 0; \ + _perfStatesSkippedProcessed = 0; \ + _lastTimeStamp = now; \ +} + + #define DUMP_TRANSSET(where) \ {\ std::cout << std::endl;\ @@ -41,26 +61,13 @@ std::cout << "** " << transitions.size() << " ** " << where << std::endl;\ std::cout << transitions[m] << std::endl;\ }\ } + namespace uscxml { using namespace Arabica::DOM; using namespace Arabica::XPath; -#define CREATE_TRANSIENT_STATE_WITH_CHILDS(stateId) \ -if (childs.size() > 0) { \ - Element transientState = _flatDoc.createElementNS(_nsInfo.nsURL, "state"); \ - _nsInfo.setPrefix(transientState);\ - transientState.setAttribute("transient", "true"); \ - if (stateId.length() > 0) \ - transientState.setAttribute("id", stateId); \ - for (int i = 0; i < childs.size(); i++) { \ - Node imported = _flatDoc.importNode(childs[i], true); \ - transientState.appendChild(imported); \ - } \ - transientStateChain.push_back(transientState); \ -} \ -childs = NodeSet(); #define DETAIL_EXEC_CONTENT(field, actPtr) \ std::cerr << " " << #field << " / " << TAGNAME_CAST(actPtr->field) << " ("; \ @@ -71,19 +78,7 @@ for (int i = 0; i < contents.size(); i++) { \ std::cerr << ")"; -Interpreter ChartToFSM::flatten(const Interpreter& other) { - - // instantiate a new InterpreterImpl to do the flattening - boost::shared_ptr flattener = boost::shared_ptr(new FlatteningInterpreter(other.getDocument())); - flattener->setNameSpaceInfo(other.getNameSpaceInfo()); - flattener->interpret(); - - // clone the flattener implementation to a new interpreter - Interpreter flat = Interpreter::fromClone(flattener); - return flat; -} - -uint64_t ChartToFSM::stateMachineComplexity(const Arabica::DOM::Element& root) { +uint64_t Complexity::stateMachineComplexity(const Arabica::DOM::Element& root) { Complexity complexity = calculateStateMachineComplexity(root); uint64_t value = complexity.value; for (std::list::const_iterator histIter = complexity.history.begin(); histIter != complexity.history.end(); histIter++) { @@ -93,7 +88,7 @@ uint64_t ChartToFSM::stateMachineComplexity(const Arabica::DOM::Element& root) { +Complexity Complexity::calculateStateMachineComplexity(const Arabica::DOM::Element& root) { Complexity complexity; bool hasFlatHistory = false; @@ -144,56 +139,66 @@ ChartToFSM::Complexity ChartToFSM::calculateStateMachineComplexity(const Arabica } -FlatteningInterpreter::FlatteningInterpreter(const Document& doc) { +ChartToFSM::ChartToFSM(const Interpreter& other) { - _perfProcessed = 0; - _perfTotal = 0; + cloneFrom(other.getImpl()); + _lastTimeStamp = tthread::chrono::system_clock::now(); + _perfTransProcessed = 0; + _perfTransTotal = 0; + _perfStatesProcessed = 0; + _perfStatesSkippedProcessed = 0; + _perfStatesSkippedTotal = 0; + _perfStatesCachedProcessed = 0; + _perfStatesCachedTotal = 0; + + _start = NULL; _currGlobalTransition = NULL; _lastTransientStateId = 0; - // just copy given doc into _document an create _flatDoc for the FSM + _lastStateIndex = 0; + _lastTransIndex = 0; + + // create a _flatDoc for the FSM DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); - _document = domFactory.createDocument(doc.getNamespaceURI(), "", 0); - _flatDoc = domFactory.createDocument(doc.getNamespaceURI(), "", 0); - - Node child = doc.getFirstChild(); - while (child) { - Node newNode = _document.importNode(child, true); - _document.appendChild(newNode); - child = child.getNextSibling(); - } + _flatDoc = domFactory.createDocument(other.getDocument().getNamespaceURI(), "", 0); addMonitor(this); } -FlatteningInterpreter::~FlatteningInterpreter() { +ChartToFSM::~ChartToFSM() { std::map::iterator confIter = _globalConf.begin(); while(confIter != _globalConf.end()) { - std::map::iterator transIter = confIter->second->outgoing.begin(); - while (transIter != confIter->second->outgoing.end()) { - delete transIter->second; + std::list::iterator transIter = confIter->second->sortedOutgoing.begin(); + while (transIter != confIter->second->sortedOutgoing.end()) { + delete *transIter; transIter++; } delete confIter->second; confIter++; } + + // tear down caches + Arabica::XPath::NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + for (int i = 0; i < allTransitions.size(); i++) { + _transParents.erase(allTransitions[i]); + } } -Document FlatteningInterpreter::getDocument() const { +Document ChartToFSM::getDocument() const { // std::cerr << "######################" << std::endl; // std::cerr << _flatDoc << std::endl; // std::cerr << "######################" << std::endl; return _flatDoc; } -InterpreterState FlatteningInterpreter::interpret() { +InterpreterState ChartToFSM::interpret() { init(); setupIOProcessors(); - uint64_t complexity = ChartToFSM::stateMachineComplexity(_scxml) + 1; + uint64_t complexity = Complexity::stateMachineComplexity(_scxml) + 1; std::cerr << "Approximate Complexity: " << complexity << std::endl; // initialize the datamodel @@ -216,6 +221,16 @@ InterpreterState FlatteningInterpreter::interpret() { LOG(ERROR) << "No datamodel for " << datamodelName << " registered"; } + // setup caches + { + Arabica::XPath::NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + indexedTransitions.reserve(allTransitions.size()); + for (int i = 0; i < allTransitions.size(); i++) { + _transParents[allTransitions[i]] = InterpreterImpl::getParentState(allTransitions[i]); + } + } + + _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); // set invokeid for all invokers to parent state if none given @@ -229,10 +244,9 @@ InterpreterState FlatteningInterpreter::interpret() { _currGlobalTransition = NULL; // very first state - GlobalState::gIndex = 0; - _start = new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix); + _start = new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this); _globalConf[_start->stateId] = _start; - _globalConf[_start->stateId]->index = toStr(GlobalState::gIndex++); + _globalConf[_start->stateId]->index = _lastStateIndex++; NodeSet initialTransitions; @@ -255,14 +269,47 @@ InterpreterState FlatteningInterpreter::interpret() { // weightTransitions(); indexTransitions(_scxml); + // reverse indices for most prior to be in front + std::reverse(indexedTransitions.begin(), indexedTransitions.end()); + + // add initial transitions as least prior + for (int i = 0; i < initialTransitions.size() ; i++) { + indexedTransitions.push_back(Element(initialTransitions[i])); + } + + // set index attribute for transitions + for (int i = 0; i < indexedTransitions.size(); i++) { + indexedTransitions[i].setAttribute("index", toStr(i)); + } + +// int lastTransIndex = indexedTransitions.size(); +// for (int i = 0; i < initialTransitions.size() ; i++, lastTransIndex++) { +// indexedTransitions[i].setAttribute("index", toStr(indexedTransitions.size() - 1 - i)); +// } + + // gather and set index attribute o states + NodeSet allStates = getAllStates(); + allStates.to_document_order(); + + indexedStates.resize(allStates.size()); + for (int i = 0; i < allStates.size(); i++) { + Element state = Element(allStates[i]); + state.setAttribute("index", toStr(i)); + indexedStates[i] = state; + } + // std::cerr << _scxml << std::endl; GlobalTransition* globalTransition = new GlobalTransition(initialTransitions, _dataModel, this); - _start->outgoing[globalTransition->transitionId] = globalTransition; + globalTransition->index = _lastTransIndex++; + + _start->sortedOutgoing.push_back(globalTransition); globalTransition->source = _start->stateId; _currGlobalTransition = globalTransition; enterStates(initialTransitions); + globalTransition->destination = FlatStateIdentifier::toStateId(_configuration); + explode(); #if 0 @@ -275,9 +322,6 @@ InterpreterState FlatteningInterpreter::interpret() { std::cerr << _globalConf.size() << std::endl; #endif - createDocument(); - -// std::cout << _scxml << std::endl; NodeSet elements = InterpreterImpl::filterChildType(Node_base::ELEMENT_NODE, _scxml, true); uint64_t nrStates = 0; @@ -294,14 +338,14 @@ InterpreterState FlatteningInterpreter::interpret() { return _state; } -void FlatteningInterpreter::executeContent(const Arabica::DOM::Element& content, bool rethrow) { +void ChartToFSM::executeContent(const Arabica::DOM::Element& content, bool rethrow) { // std::cerr << content << std::endl; // std::cerr << TAGNAME(content) << std::endl; GlobalTransition::Action action; if (false) { - } else if (TAGNAME(content) == "transition") { + } else if (TAGNAME(content) == "transition" && content.hasChildNodes()) { action.transition = content; } else if (TAGNAME(content) == "onexit") { action.onExit = content; @@ -313,23 +357,24 @@ void FlatteningInterpreter::executeContent(const Arabica::DOM::Elementactions.push_back(action); + _currGlobalTransition->hasExecutableContent = true; } -void FlatteningInterpreter::invoke(const Arabica::DOM::Element& element) { +void ChartToFSM::invoke(const Arabica::DOM::Element& element) { GlobalTransition::Action action; action.invoke = element; _currGlobalTransition->actions.push_back(action); - _currGlobalTransition->invoke.push_back(element); + _currGlobalTransition->hasExecutableContent = true; } -void FlatteningInterpreter::cancelInvoke(const Arabica::DOM::Element& element) { +void ChartToFSM::cancelInvoke(const Arabica::DOM::Element& element) { GlobalTransition::Action action; action.uninvoke = element; _currGlobalTransition->actions.push_back(action); - _currGlobalTransition->uninvoke.push_back(element); + _currGlobalTransition->hasExecutableContent = true; } -void FlatteningInterpreter::internalDoneSend(const Arabica::DOM::Element& state) { +void ChartToFSM::internalDoneSend(const Arabica::DOM::Element& state) { Arabica::DOM::Element stateElem = (Arabica::DOM::Element)state; if (parentIsScxmlState(state)) @@ -367,34 +412,38 @@ void FlatteningInterpreter::internalDoneSend(const Arabica::DOM::Elementactions.push_back(action); + _currGlobalTransition->hasExecutableContent = true; } static bool isSuperset(const GlobalTransition* t1, const GlobalTransition* t2) { bool isSuperset = true; - if (t1->transitions.size() >= t2->transitions.size()) + if (t1->transitionRefs.size() >= t2->transitionRefs.size()) return false; - for (int i = 0; i < t1->transitions.size(); i++) { - if (!InterpreterImpl::isMember(t1->transitions[i], t2->transitions)) { + NodeSet t1Trans = t1->getTransitions(); + NodeSet t2Trans = t2->getTransitions(); + + for (int i = 0; i < t1Trans.size(); i++) { + if (!InterpreterImpl::isMember(t1Trans[i], t2Trans)) { isSuperset = false; } } return isSuperset; } -static bool filterSameState(const NodeSet& transitions) { - NodeSet filteredTransitions; +// return false if two transitions have the same source +std::map, Arabica::DOM::Node > ChartToFSM::_transParents; +bool ChartToFSM::filterSameState(const NodeSet& transitions) { + for (unsigned int i = 0; i < transitions.size(); i++) { - Node t1 = transitions[i]; - Node p1 = InterpreterImpl::getParentState(t1); + Node p1 = _transParents[transitions[i]]; - for (unsigned int j = 0; j < transitions.size(); j++) { - if (i == j) - continue; - Node t2 = transitions[j]; - Node p2 = InterpreterImpl::getParentState(t2); + for (unsigned int j = i + 1; j < transitions.size(); j++) { +// if (i == j) +// continue; + Node p2 = _transParents[transitions[j]]; if (p1 == p2) return false; @@ -433,7 +482,7 @@ static bool filterChildEnabled(const NodeSet& transitions) { } -void FlatteningInterpreter::indexTransitions(const Arabica::DOM::Element& root) { +void ChartToFSM::indexTransitions(const Arabica::DOM::Element& root) { // breadth first traversal of transitions Arabica::XPath::NodeSet levelTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", root); for (int i = levelTransitions.size() - 1; i >= 0; i--) { @@ -450,187 +499,163 @@ void FlatteningInterpreter::indexTransitions(const Arabica::DOM::Element >& indexedTransitions = interpreter->indexedTransitions; - for (std::list >::const_reverse_iterator transIter = indexedTransitions.rbegin(); transIter != indexedTransitions.rend(); transIter++) { + const std::vector >& indexedTransitions = interpreter->indexedTransitions; + NodeSet transitions = getTransitions(); + + for (std::vector >::const_iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { const Element& refTrans = *transIter; + NodeSet otherTransitions = other.getTransitions(); - if (InterpreterImpl::isMember(refTrans, transitions) && !InterpreterImpl::isMember(refTrans, other.transitions)) { + if (InterpreterImpl::isMember(refTrans, transitions) && !InterpreterImpl::isMember(refTrans, otherTransitions)) { return true; } - if (!InterpreterImpl::isMember(refTrans, transitions) && InterpreterImpl::isMember(refTrans, other.transitions)) { + if (!InterpreterImpl::isMember(refTrans, transitions) && InterpreterImpl::isMember(refTrans, otherTransitions)) { return false; } } return true; // actually, they are equal } +template bool PtrComp(const T * const & a, const T * const & b) { + return *a < *b; +} -void FlatteningInterpreter::explode() { - - // save global configuration elements to restore after recursive explode - Arabica::XPath::NodeSet configuration = _configuration; - Arabica::XPath::NodeSet alreadyEntered = _alreadyEntered; - std::map > historyValue = _historyValue; - - // create current state from global configuration - GlobalState* globalState = new GlobalState(configuration, alreadyEntered, historyValue, _nsInfo.xmlNSPrefix); - // remember that the last transition lead here - if (_currGlobalTransition) { - _currGlobalTransition->destination = globalState->stateId; - globalState->incoming[_currGlobalTransition->transitionId] = _currGlobalTransition; +/** + * subset only removes transitions without cond -> superset will always be enabled + */ +bool hasUnconditionalSuperset (GlobalTransition* first, GlobalTransition* second) { -// if (!_currGlobalTransition->isEventless) { - // if it was an eventful transition, add all invokers - for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { - NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); - for (unsigned int j = 0; j < invokes.size(); j++) { - invoke(Element(invokes[j])); + NodeSet firstTransitions = first->getTransitions(); + NodeSet secondTransitions = first->getTransitions(); + + if (isSuperset(second, first)) { + for (int i = 0; i < firstTransitions.size(); i++) { + if (!InterpreterImpl::isMember(firstTransitions[i], secondTransitions)) { + if (HAS_ATTR_CAST(firstTransitions[i], "cond")) { + return false; // second can't be removed + } } } - _statesToInvoke = NodeSet(); -// } + return true; // remove second } + return false; //second can't be removed +} - if (_globalConf.find(globalState->stateId) != _globalConf.end()) { - delete globalState; - return; // we have already been here +bool hasEarlierUnconditionalMatch(GlobalTransition* first, GlobalTransition* second) { + if (first->eventDesc == second->eventDesc) { + if (first->condition.size() == 0) + return true; } + return false; +} - _globalConf[globalState->stateId] = globalState; - _globalConf[globalState->stateId]->index = toStr(GlobalState::gIndex++); -// assert(isLegalConfiguration(configuration)); - - if(_globalConf[globalState->stateId]->isFinal) - return; // done in this branch +// for some reason, unique is not quite up to the task +std::list reapplyUniquePredicates(std::list list) { + + for (std::list::iterator outerIter = list.begin(); + outerIter != list.end(); + outerIter++) { + for (std::list::iterator innerIter = outerIter; + innerIter != list.end(); + innerIter++) { + + if (innerIter == outerIter) + continue; + + GlobalTransition* t1 = *outerIter; + GlobalTransition* t2 = *innerIter; + + if (hasUnconditionalSuperset(t1, t2)) { + list.erase(outerIter++); + continue; + } else if (hasUnconditionalSuperset(t2, t1)) { + list.erase(innerIter++); + continue; + } + if (hasEarlierUnconditionalMatch(t1, t2)) { + list.erase(innerIter++); + continue; + } + } + } + + return list; +} +void ChartToFSM::getPotentialTransitionsForConf(const Arabica::XPath::NodeSet& conf, std::map& outMap) { // get all transition elements from states in the current configuration - NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", configuration); - - /** - * From http://www.w3.org/TR/scxml/#SelectingTransitions - * - * A transition T is enabled by named event E in atomic state S if - * a) T's source state is S or an ancestor of S - * b) T matches E's name - * c) T lacks a 'cond' attribute or its 'cond' attribute evaluates to "true" - * - * A transition is enabled by NULL in atomic state S if - * a) T lacks an 'event' attribute - * b) T's source state is S or an ancestor of S - * c) T lacks an 'cond' attribute or its 'cond' attribute evaluates to "true" - * - * The _source state_ of a transition is the or element that it occurs in. - * The _target state(s)_ of the transition is the state or set of states specified by its 'target' attribute. - * The _complete target_ set of a transition consists of all the states that will be active after the transition is taken. - * - * A transition T is optimally enabled by event E in atomic state S if - * a) T is enabled by E in S - * b) no transition that precedes T in document order in T's source state is enabled by E in S - * c) no transition is enabled by E in S in any descendant of T's source state. - * - * Two transitions T1 and T2 conflict in state configuration C if their exit sets in C have a non-null intersection. - * let transitions T1 and T2 conflict: - * T1 is optimally enabled in atomic state S1 - * T2 is optimally enabled in atomic state S2 - * S1 and S2 are both active - * T1 has a higher priority than T2 if: - * a) T1's source state is a descendant of T2's source state, or - * b) S1 precedes S2 in document order - * - * The _optimal transition set_ enabled by event E in state configuration C is - * the largest set of transitions such that: - * a) each transition in the set is optimally enabled by E in an atomic state in C - * b) no transition conflicts with another transition in the set - * c) there is no optimally enabled transition outside the set that has a higher priority than some member of the set - * - * A _microstep_ consists of the execution of the transitions in an optimal enabled transition set - * - * A _macrostep_ is a series of one or more microsteps ending in a configuration - * where the internal event queue is empty and no transitions are enabled by NULL - */ - - + NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", conf); + if (allTransitions.size() == 0) return; // no transitions - + int nrElements = allTransitions.size(); int k = 0; int* stack = (int*)malloc((nrElements + 1) * sizeof(int)); memset(stack, 0, (nrElements + 1) * sizeof(int)); - - // subset of powerset we already processed - std::map transitionSets; - + while(1) { // create the power set of all potential transitions - this is expensive! // see: http://www.programminglogic.com/powerset-algorithm-in-c/ - + if (stack[k] < nrElements) { stack[k+1] = stack[k] + 1; k++; } - + else { stack[k-1]++; k--; } - + if (k==0) break; - + NodeSet transitions; -// std::cerr << globalState->stateId << " [" << nrElements << "]: " << std::endl; + // std::cerr << globalState->stateId << " [" << nrElements << "]: " << std::endl; for (int i = 1; i <= k; i++) { -// std::cerr << stack[i] - 1 << ", "; + // std::cerr << stack[i] - 1 << ", "; transitions.push_back(allTransitions[stack[i] - 1]); } -// std::cerr << std::endl; - -// transitions.push_back(allTransitions[0]); -// transitions.push_back(allTransitions[4]); -// transitions.push_back(allTransitions[5]); -// transitions.push_back(allTransitions[7]); - + // std::cerr << std::endl; + + // transitions.push_back(allTransitions[0]); + // transitions.push_back(allTransitions[4]); + // transitions.push_back(allTransitions[5]); + // transitions.push_back(allTransitions[7]); + bool dump = false; - -// if (k == 4 && stack[1] == 1 && stack[2] == 5 && stack[3] == 6 && stack[4] == 8) { -// dump = true; -// } - + + // if (k == 4 && stack[1] == 1 && stack[2] == 5 && stack[3] == 6 && stack[4] == 8) { + // dump = true; + // } + if (dump) DUMP_TRANSSET("at start"); - - _perfTotal++; - _perfProcessed++; - - if (tthread::chrono::system_clock::now() - _lastTimeStamp > 1000) { - _lastTimeStamp = tthread::chrono::system_clock::now(); -// std::cerr << globalState->stateId << " [" << nrElements << "]: " << std::endl; - std::cerr << "States: " << _globalConf.size() << " - "; - std::cerr << "Tested: " << _perfTotal << " [" << _perfProcessed << "/sec] - "; - std::cerr << "Current Complexity: 2**" << nrElements << " = " << pow(2.0, static_cast(nrElements)); - std::cerr << std::endl; - _perfProcessed = 0; - } - + + _perfTransTotal++; + _perfTransProcessed++; + + DUMP_STATS(nrElements); + // remove transitions in the same state if(!filterSameState(transitions)) continue; if (dump) DUMP_TRANSSET("after same state filtered"); - + // remove those transitions with a child transition if(!filterChildEnabled(transitions)) continue; if (dump) DUMP_TRANSSET("after child enabled filtered"); - + // reduce to conflict-free subset // transitions.to_document_order(); transitions = removeConflictingTransitions(transitions); if (dump) DUMP_TRANSSET("after conflicting filtered"); - + // algorithm can never reduce to empty set assert(transitions.size() > 0); - + // create a GlobalTransition object from the set GlobalTransition* transition = new GlobalTransition(transitions, _dataModel, this); if (!transition->isValid) { @@ -638,447 +663,152 @@ void FlatteningInterpreter::explode() { delete transition; continue; } + transition->index = _lastTransIndex++; // two combinations might have projected onto the same conflict-free set - if (transitionSets.find(transition->transitionId) != transitionSets.end()) { -// std::cerr << "skipping as projected onto existing conflict-free subset" << std::endl; + if (outMap.find(transition->transitionId) != outMap.end()) { + // std::cerr << "skipping as projected onto existing conflict-free subset" << std::endl; delete transition; continue; } - + // remember this conflict-free set -// std::cerr << "New conflict-free subset: " << transition->transitionId << ":" << transition->eventDesc << std::endl; - transitionSets[transition->transitionId] = transition; + // std::cerr << "New conflict-free subset: " << transition->transitionId << ":" << transition->eventDesc << std::endl; + outMap[transition->transitionId] = transition; } - - // TODO: reduce and sort transition sets - std::list transitionList; - for(std::map::iterator transSetIter = transitionSets.begin(); - transSetIter != transitionSets.end(); - transSetIter++) { - transitionList.push_back(transSetIter->second); - } - - for(std::list::iterator transListIter = transitionList.begin(); - transListIter != transitionList.end(); - transListIter++) { - - // add transition set to current global state - globalState->outgoing[(*transListIter)->transitionId] = *transListIter; - (*transListIter)->source = globalState->stateId; - - _currGlobalTransition = *transListIter; - microstep((*transListIter)->transitions); -// if (!isLegalConfiguration(_configuration)) { -// FlatStateIdentifier fromState(configuration, alreadyEntered, historyValue); -// FlatStateIdentifier toState(_configuration, _alreadyEntered, _historyValue); -// std::cerr << "invalid configuration after transition " << std::endl -// << "from \t" << fromState.getStateId() << std::endl -// << "to \t" << toState.getStateId() << std::endl -// << "via ------" << std::endl; -// for (int i = 0; i < (*transListIter)->transitions.size(); i++) { -// std::cerr << (*transListIter)->transitions[i] << std::endl; -// } -// std::cerr << "----------" << std::endl; -// assert(false); -// } - explode(); - - // reset state for next transition set - _configuration = configuration; - _alreadyEntered = alreadyEntered; - _historyValue = historyValue; - - } -} - -static bool sortStatesByIndex(const std::pair& s1, const std::pair& s2) { - return s1.second->index < s2.second->index; + return; } - -void FlatteningInterpreter::createDocument() { - Element _origSCXML = _scxml; - - _scxml = _flatDoc.createElementNS(_nsInfo.nsURL, "scxml"); - _nsInfo.setPrefix(_scxml); - - _scxml.setAttribute("flat", "true"); - _flatDoc.appendChild(_scxml); - - if (HAS_ATTR(_origSCXML, "datamodel")) { - _scxml.setAttribute("datamodel", ATTR(_origSCXML, "datamodel")); - } - - if (HAS_ATTR(_origSCXML, "name")) { - _scxml.setAttribute("name", ATTR(_origSCXML, "name")); - } - - if (HAS_ATTR(_origSCXML, "binding")) { - _scxml.setAttribute("binding", ATTR(_origSCXML, "binding")); - } - - _scxml.setAttribute("initial", _start->stateId); - - NodeSet datas; - if (_binding == InterpreterImpl::LATE) { - // with late binding, just copy direct datamodel childs - datas = filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", _origSCXML); - } else { - // with early binding, copy all datamodel elements into scxml element - datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "datamodel", _origSCXML).asNodeSet(); - } - for (int i = 0; i < datas.size(); i++) { - if (isInEmbeddedDocument(datas[i])) - continue; // nested document - Node imported = _flatDoc.importNode(datas[i], true); - _scxml.appendChild(imported); - } - - - NodeSet scripts = filterChildElements(_nsInfo.xmlNSPrefix + "script", _origSCXML); - for (int i = 0; i < scripts.size(); i++) { - Node imported = _flatDoc.importNode(scripts[i], true); - _scxml.appendChild(imported); - } - - NodeSet comments = filterChildType(Node_base::COMMENT_NODE, _origSCXML); - for (int i = 0; i < comments.size(); i++) { - Node imported = _flatDoc.importNode(comments[i], true); - _scxml.appendChild(imported); - } - - std::vector > sortedStates; - sortedStates.insert(sortedStates.begin(), _globalConf.begin(), _globalConf.end()); - std::sort(sortedStates.begin(), sortedStates.end(), sortStatesByIndex); - - int index = 0; - for (std::list >::reverse_iterator transIter = indexedTransitions.rbegin(); transIter != indexedTransitions.rend(); transIter++) { - const Element& refTrans = *transIter; - std::cerr << index++ << ": " << refTrans << std::endl; - } - std::cerr << std::endl; - - for (std::vector >::iterator confIter = sortedStates.begin(); - confIter != sortedStates.end(); - confIter++) { - appendGlobalStateNode(confIter->second); - } -// exit(0); - -} - - -template bool PtrComp(const T * const & a, const T * const & b) { - return *a < *b; -} - - -/** - * subset only removes transitions without cond -> superset will always be enabled - */ -bool hasUnconditionalSuperset (GlobalTransition* first, GlobalTransition* second) { - if (isSuperset(second, first)) { - for (int i = 0; i < first->transitions.size(); i++) { - if (!InterpreterImpl::isMember(first->transitions[i], second->transitions)) { - if (HAS_ATTR_CAST(first->transitions[i], "cond")) { - return false; // second can't be removed - } - } - } - return true; // remove second - } - return false; //second can't be removed -} - -bool hasEarlierUnconditionalMatch(GlobalTransition* first, GlobalTransition* second) { - if (first->eventDesc == second->eventDesc) { - if (first->condition.size() == 0) - return true; - } - return false; -} - -// for some reason, unique is not quite up to the task -std::list reapplyUniquePredicates(std::list list) { - - for (std::list::iterator outerIter = list.begin(); - outerIter != list.end(); - outerIter++) { - for (std::list::iterator innerIter = outerIter; - innerIter != list.end(); - innerIter++) { - - if (innerIter == outerIter) - continue; - - GlobalTransition* t1 = *outerIter; - GlobalTransition* t2 = *innerIter; - - if (hasUnconditionalSuperset(t1, t2)) { - list.erase(outerIter++); - continue; - } else if (hasUnconditionalSuperset(t2, t1)) { - list.erase(innerIter++); - continue; - } - if (hasEarlierUnconditionalMatch(t1, t2)) { - list.erase(innerIter++); - continue; - } + +void ChartToFSM::explode() { + + std::list statesRemaining; + statesRemaining.push_back(new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this)); + + // add all invokers for initial transition + for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { + NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); + for (unsigned int j = 0; j < invokes.size(); j++) { + invoke(Element(invokes[j])); } } + _statesToInvoke = NodeSet(); - return list; -} - -void FlatteningInterpreter::appendGlobalStateNode(GlobalState* globalState) { - Element state = _flatDoc.createElementNS(_nsInfo.nsURL, "state"); - _nsInfo.setPrefix(state); - - state.setAttribute("ref", globalState->index); - state.setAttribute("id", globalState->stateId); - - if (globalState->isFinal) - state.setAttribute("final", "true"); - - std::list transitionList; - for (std::map::iterator outIter = globalState->outgoing.begin(); - outIter != globalState->outgoing.end(); - outIter++) { - transitionList.push_back(outIter->second); - } - -// transitionList = sortTransitions(transitionList); - transitionList.sort(PtrComp); - transitionList.unique(hasUnconditionalSuperset); - transitionList.unique(hasEarlierUnconditionalMatch); - // unique is not quite like what we need, but it was a start - transitionList = reapplyUniquePredicates(transitionList); - - // apend here, for transient state chains to trail the state - _scxml.appendChild(state); - - size_t index = 0; - for (std::list::iterator outIter = transitionList.begin(); - outIter != transitionList.end(); - outIter++) { - (*outIter)->index = globalState->index + ":" + toStr(index); - state.appendChild(globalTransitionToNode(*outIter)); - index++; - } -} - -/** - * Creates transient states for executable content as a side-effect - */ -Node FlatteningInterpreter::globalTransitionToNode(GlobalTransition* globalTransition) { - Element transition = _flatDoc.createElementNS(_nsInfo.nsURL, "transition"); - _nsInfo.setPrefix(transition); - -// transition.setAttribute("ref", globalTransition->index); - -#if 1 - transition.setAttribute("members", globalTransition->members); -#endif - // transition.setAttribute("priority", toStr(globalTransition->priority)); - - if (!globalTransition->isEventless) { - transition.setAttribute("event", globalTransition->eventDesc); - } - - if (globalTransition->condition.size() > 0) { - transition.setAttribute("cond", globalTransition->condition); - } - - if (globalTransition->destination.size() > 0) { - transition.setAttribute("final-target", globalTransition->destination); - } - - NodeSet transientStateChain; - - // current active state set - FlatStateIdentifier flatId(globalTransition->source); - std::list currActiveStates = flatId.getActive(); - -// std::cerr << "From " << globalTransition->source << " to " << globalTransition->destination << ":" << std::endl; - - // gather content for new transient state - NodeSet childs; - // iterate all actions taken during the transition - for (std::list::iterator actionIter = globalTransition->actions.begin(); - actionIter != globalTransition->actions.end(); - actionIter++) { - - if (actionIter->transition) { -// DETAIL_EXEC_CONTENT(transition, actionIter); - - Element onexit = _flatDoc.createElementNS(_nsInfo.nsURL, "onexit"); - _nsInfo.setPrefix(onexit); - Node child = actionIter->transition.getFirstChild(); - while(child) { - Node imported = _flatDoc.importNode(child, true); - onexit.appendChild(imported); - child = child.getNextSibling(); - } - // only append if there is something done - if (onexit.hasChildNodes()) - childs.push_back(onexit); - - continue; - } - - if (actionIter->onExit) { -// DETAIL_EXEC_CONTENT(onExit, actionIter); - - childs.push_back(actionIter->onExit); - continue; - } - - if (actionIter->onEntry) { -// DETAIL_EXEC_CONTENT(onEntry, actionIter); - - childs.push_back(actionIter->onEntry); - continue; - } - - if (actionIter->invoke) { -// DETAIL_EXEC_CONTENT(invoke, actionIter); - if (!globalTransition->isTargetless) { -// CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); - } - Element invokeElem = Element(actionIter->invoke); - invokeElem.setAttribute("persist", "true"); - childs.push_back(invokeElem); - continue; + /** + We need this to be a recursion in order not to exhaust the stack + */ + + // append new global states and pop from front + while(statesRemaining.size() > 0) { + DUMP_STATS(0); + + GlobalState* globalState = statesRemaining.front(); + statesRemaining.pop_front(); + + // used to be conditionalized, we will just assum + assert(_currGlobalTransition); + + if (_globalConf.find(globalState->stateId) != _globalConf.end()) { + delete globalState; + _perfStatesSkippedTotal++; + _perfStatesSkippedProcessed++; + continue; // we have already been here } - if (actionIter->uninvoke) { -// DETAIL_EXEC_CONTENT(uninvoke, actionIter); - Element uninvokeElem = _flatDoc.createElementNS(_nsInfo.nsURL, "uninvoke"); - _nsInfo.setPrefix(uninvokeElem); - - if (HAS_ATTR(actionIter->uninvoke, "type")) { - uninvokeElem.setAttribute("type", ATTR(actionIter->uninvoke, "type")); - } - if (HAS_ATTR(actionIter->uninvoke, "typeexpr")) { - uninvokeElem.setAttribute("typeexpr", ATTR(actionIter->uninvoke, "typeexpr")); - } - if (HAS_ATTR(actionIter->uninvoke, "id")) { - uninvokeElem.setAttribute("id", ATTR(actionIter->uninvoke, "id")); + _perfStatesProcessed++; + + _configuration = globalState->getActiveStates(); + _alreadyEntered = globalState->getAlreadyEnteredStates(); + _historyValue = globalState->getHistoryStates(); + + // remember as global configuration + _globalConf[globalState->stateId] = globalState; + _globalConf[globalState->stateId]->index = _lastStateIndex++; + + if(_globalConf[globalState->stateId]->isFinal) + continue; // done in this branch + + if (_transPerActiveConf.find(globalState->activeId) != _transPerActiveConf.end()) { + // we already know these transition sets, just copy over + std::list::iterator sortTransIter = _transPerActiveConf[globalState->activeId]->sortedOutgoing.begin(); + while(sortTransIter != _transPerActiveConf[globalState->activeId]->sortedOutgoing.end()) { + globalState->sortedOutgoing.push_back(new GlobalTransition(**sortTransIter)); // copy constructor + globalState->sortedOutgoing.back()->index = _lastTransIndex++; + sortTransIter++; } - if (HAS_ATTR(actionIter->uninvoke, "idlocation")) { - uninvokeElem.setAttribute("idlocation", ATTR(actionIter->uninvoke, "idlocation")); - } - childs.push_back(uninvokeElem); - continue; - } + _perfStatesCachedTotal++; + _perfStatesCachedProcessed++; - if (actionIter->exited) { -// std::cerr << " exited(" << ATTR_CAST(actionIter->exited, "id") << ")"; - currActiveStates.remove(ATTR_CAST(actionIter->exited, "id")); - if (childs.size() > 0) { - CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); // create a new transient state to update its id + } else { + // we need to calculate the potential optimal transition sets + std::map transitionSets; + getPotentialTransitionsForConf(refsToStates(globalState->activeStatesRefs), transitionSets); + + // TODO: reduce and sort transition sets + for(std::map::iterator transSetIter = transitionSets.begin(); + transSetIter != transitionSets.end(); + transSetIter++) { + globalState->sortedOutgoing.push_back(transSetIter->second); } + + globalState->sortedOutgoing.sort(PtrComp); + globalState->sortedOutgoing.unique(hasUnconditionalSuperset); + globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); + // unique is not quite like what we need, but it was a start + globalState->sortedOutgoing = reapplyUniquePredicates(globalState->sortedOutgoing); + + _transPerActiveConf[globalState->activeId] = globalState; } - - if (actionIter->entered) { -// std::cerr << " entered(" << ATTR_CAST(actionIter->entered, "id") << ")"; - if (childs.size() > 0) - CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); // create a new transient state to update its id - currActiveStates.push_back(ATTR_CAST(actionIter->entered, "id")); - - // we entered a new child - check if it has a datamodel and we entered for the first time - if (_binding == InterpreterImpl::LATE) { - NodeSet datamodel = filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", actionIter->entered); - if (datamodel.size() > 0 && !isMember(actionIter->entered, _globalConf[globalTransition->source]->alreadyEnteredStates)) { - childs.push_back(datamodel); + + // append resulting new states + for(std::list::iterator transListIter = globalState->sortedOutgoing.begin(); + transListIter != globalState->sortedOutgoing.end(); + transListIter++) { + + (*transListIter)->source = globalState->stateId; + _currGlobalTransition = *transListIter; + + microstep(refsToTransitions((*transListIter)->transitionRefs)); + statesRemaining.push_back(new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this)); + + // add all invokers + for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { + NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); + for (unsigned int j = 0; j < invokes.size(); j++) { + invoke(Element(invokes[j])); } } - } - if (!globalTransition->isTargetless) { -// CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) - } - } - -// std::cerr << std::endl; - -// if (globalTransition->isTargetless) { -// for (int i = 0; i < childs.size(); i++) { -// Node imported = _flatDoc.importNode(childs[i], true); -// transition.appendChild(imported); -// // CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) -// } -// return transition; -// } - - CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) - - if (transientStateChain.size() > 0) { - Element prevExitTransitionElem; - - for (int i = 0; i < transientStateChain.size(); i++) { - Element transientStateElem = Element(transientStateChain[i]); - transientStateElem.setAttribute("id", transientStateElem.getAttribute("id") + "-via-" + toStr(_lastTransientStateId++)); - - Element exitTransition = _flatDoc.createElementNS(_nsInfo.nsURL, "transition"); - _nsInfo.setPrefix(exitTransition); - - if (prevExitTransitionElem) { - // point previous to this one - prevExitTransitionElem.setAttribute("target", transientStateElem.getAttribute("id")); - } else { - // update globalTransition->source target - } + _statesToInvoke = NodeSet(); - transientStateElem.appendChild(exitTransition); - prevExitTransitionElem = exitTransition; + // remember that the last transition lead here + (*transListIter)->destination = statesRemaining.back()->stateId; - if (i == 0) - transition.setAttribute("target", transientStateElem.getAttribute("id")); - - _scxml.appendChild(transientStateElem); + // reset state for next transition set + _configuration = globalState->getActiveStates(); + _alreadyEntered = globalState->getAlreadyEnteredStates(); + _historyValue = globalState->getHistoryStates(); } - - // last one points to actual target - assert(prevExitTransitionElem); - prevExitTransitionElem.setAttribute("target", globalTransition->destination); - - } else { - transition.setAttribute("target", globalTransition->destination); } - assert(HAS_ATTR_CAST(transition, "target")); - return transition; } -#if 0 -void FlatteningInterpreter::weightTransitions() { - maxDepth = 0; - maxOrder = 0; - - int depth = 0; - Arabica::XPath::NodeSet states = getChildStates(_scxml); - while(states.size() > 0) { - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", states); - NodeSet initials = filterChildElements(_nsInfo.xmlNSPrefix + "initial", states); - transitions.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "transition", initials)); +Arabica::XPath::NodeSet ChartToFSM::refsToStates(const std::set& stateRefs) { + NodeSet states; + for (std::set::const_iterator stateIter = stateRefs.begin(); stateIter != stateRefs.end(); stateIter++) { + states.push_back(indexedStates[*stateIter]); + } + return states; +} - for (int j = 0; j < transitions.size(); j++) { - if (depth > maxDepth) - maxDepth = depth; - if (j > maxOrder) - maxOrder = j; - Element transition = Element(transitions[j]); - transition.setAttribute("depth", toStr(depth)); - transition.setAttribute("order", toStr(j)); - } - depth++; - states = getChildStates(states); +Arabica::XPath::NodeSet ChartToFSM::refsToTransitions(const std::set& transRefs) { + NodeSet transitions; + for (std::set::const_iterator transIter = transRefs.begin(); transIter != transRefs.end(); transIter++) { + transitions.push_back(indexedTransitions[*transIter]); } + return transitions; } -#endif -void FlatteningInterpreter::labelTransitions() { + +void ChartToFSM::labelTransitions() { // put a unique id on each transition Arabica::XPath::NodeSet states = getAllStates(); states.push_back(_scxml); @@ -1096,39 +826,50 @@ void FlatteningInterpreter::labelTransitions() { } } -void FlatteningInterpreter::beforeMicroStep(Interpreter interpreter) { +void ChartToFSM::beforeMicroStep(Interpreter interpreter) { } -void FlatteningInterpreter::onStableConfiguration(Interpreter interpreter) { +void ChartToFSM::onStableConfiguration(Interpreter interpreter) { } -void FlatteningInterpreter::beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { +void ChartToFSM::beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { GlobalTransition::Action action; action.exited = state; _currGlobalTransition->actions.push_back(action); - _currGlobalTransition->exited.push_back(state); } -void FlatteningInterpreter::beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { +void ChartToFSM::beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { GlobalTransition::Action action; action.entered = state; _currGlobalTransition->actions.push_back(action); - _currGlobalTransition->entered.push_back(state); } -void FlatteningInterpreter::beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) { +void ChartToFSM::beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) { } -int GlobalState::gIndex = 0; GlobalState::GlobalState(const Arabica::XPath::NodeSet& activeStates_, const Arabica::XPath::NodeSet& alreadyEnteredStates_, // we need to remember for binding=late const std::map >& historyStates_, - const std::string& xmlNSPrefix) { + const std::string& xmlNSPrefix, + ChartToFSM* flattener) { + interpreter = flattener; + + // take references + for (int i = 0; i < activeStates_.size(); i++) { + activeStatesRefs.insert(strTo(ATTR_CAST(activeStates_[i], "index"))); + } + + for (int i = 0; i < alreadyEnteredStates_.size(); i++) { + alreadyEnteredStatesRefs.insert(strTo(ATTR_CAST(alreadyEnteredStates_[i], "index"))); + } + + for (std::map >::const_iterator histIter = historyStates_.begin(); histIter != historyStates_.end(); histIter++) { + for (int i = 0; i < histIter->second.size(); i++) { + historyStatesRefs[histIter->first].insert(strTo(ATTR_CAST(histIter->second[i], "index"))); + } + } - // make copies and sort - activeStates = activeStates_; - alreadyEnteredStates = alreadyEnteredStates_; - historyStates = historyStates_; isFinal = false; - for(int i = 0; i < activeStates.size(); i++) { - Arabica::DOM::Element state = Arabica::DOM::Element(activeStates[i]); + // is state this final? + for(int i = 0; i < activeStates_.size(); i++) { + Arabica::DOM::Element state = Arabica::DOM::Element(activeStates_[i]); Arabica::DOM::Element parentElem = (Arabica::DOM::Element)state.getParentNode(); if(InterpreterImpl::isFinal(state) && iequals(parentElem.getTagName(), xmlNSPrefix + "scxml")) { isFinal = true; @@ -1136,25 +877,30 @@ GlobalState::GlobalState(const Arabica::XPath::NodeSet& activeState } } - // sort configuration - activeStates.to_document_order(); - alreadyEnteredStates.to_document_order(); - for(std::map >::iterator historyIter = historyStates.begin(); - historyIter != historyStates.end(); - historyIter++) { - historyIter->second.to_document_order(); - } - - FlatStateIdentifier flatStateId(activeStates, alreadyEnteredStates, historyStates); + FlatStateIdentifier flatStateId(getActiveStates(), getAlreadyEnteredStates(), getHistoryStates()); stateId = flatStateId.getStateId(); + activeId = flatStateId.getFlatActive(); } -GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& transitionSet, DataModel dataModel, FlatteningInterpreter* flattener) { +GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& transitionSet, DataModel dataModel, ChartToFSM* flattener) { interpreter = flattener; - transitions = transitionSet; + + for (int i = 0; i < transitionSet.size(); i++) { + transitionRefs.insert(strTo(ATTR_CAST(transitionSet[i], "index"))); + } + + std::ostringstream setId; // also build id for subset + std::string seperator = ""; + for (std::set::iterator transIter = transitionRefs.begin(); transIter != transitionRefs.end(); transIter++) { + setId << seperator << *transIter; + seperator = "-"; + } + transitionId = setId.str(); + + hasExecutableContent = false; isValid = true; isEventless = true; - + #if 0 std::cerr << "################" << std::endl; for (int i = 0; i < transitions.size(); i++) { @@ -1163,39 +909,7 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t std::cerr << "################" << std::endl; #endif - std::list conditions; - std::ostringstream setId; // also build id for subset - for (int i = 0; i < transitions.size(); i++) { - Arabica::DOM::Element transElem = Arabica::DOM::Element(transitions[i]); - // get a unique string for the transition - we assume it is sorted - assert(HAS_ATTR(transElem, "id")); - setId << ATTR(transElem, "id") << "-"; - - // gather conditions while we are iterating anyway - if (HAS_ATTR(transElem, "cond")) { - conditions.push_back(ATTR(transElem, "cond")); - } - } - transitionId = setId.str(); - - int index = 0; - std::string seperator; - for (std::list >::iterator transIter = interpreter->indexedTransitions.begin(); transIter != interpreter->indexedTransitions.end(); transIter++) { - const Element& refTrans = *transIter; - if (InterpreterImpl::isMember(refTrans, transitions)) { - members += seperator + toStr(index); - } else { - members += seperator; - for (int i = 0; i < toStr(index).size(); i++) { - members += " "; - } - } - seperator = " "; - index++; - } - -// if (members == " 4 6 7 ") -// std::cout << "asdfadf"; + // first establish whether this is a valid set /** * Can these events event occur together? They can't if: @@ -1209,8 +923,8 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t bool foundWithTarget = false; bool foundTargetLess = false; - for (int i = 0; i < transitions.size(); i++) { - Arabica::DOM::Element transElem = Arabica::DOM::Element(transitions[i]); + for (int i = 0; i < transitionSet.size(); i++) { + Arabica::DOM::Element transElem = Arabica::DOM::Element(transitionSet[i]); if (HAS_ATTR(transElem, "eventexpr")) { ERROR_EXECUTION_THROW("Cannot flatten document with eventexpr attributes"); } @@ -1253,7 +967,7 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t // is there a set of event names that would enable this conflict-free transition set? if (foundWithEvent) { // get the set of longest event descriptors that will enable this transition set - eventNames = getCommonEvents(transitions); + eventNames = getCommonEvents(transitionSet); if (eventNames.size() == 0) { // LOG(INFO) << "No event will activate this conflict-free subset" << std::endl; isValid = false; @@ -1271,6 +985,35 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t eventDesc = "*"; } + // extract conditions + std::list conditions; + for (int i = 0; i < transitionSet.size(); i++) { + Arabica::DOM::Element transElem = Arabica::DOM::Element(transitionSet[i]); + // gather conditions while we are iterating anyway + if (HAS_ATTR(transElem, "cond")) { + conditions.push_back(ATTR(transElem, "cond")); + } + } + + int index = 0; + seperator = ""; + for (std::vector >::iterator transIter = interpreter->indexedTransitions.begin(); transIter != interpreter->indexedTransitions.end(); transIter++) { + const Element& refTrans = *transIter; + if (InterpreterImpl::isMember(refTrans, transitionSet)) { + members += seperator + toStr(index); + } else { + members += seperator; + for (int i = 0; i < toStr(index).size(); i++) { + members += " "; + } + } + seperator = " "; + index++; + } + + // if (members == " 4 6 7 ") + // std::cout << "asdfadf"; + if (conditions.size() > 1) { condition = dataModel.andExpressions(conditions); if (condition.size() == 0) { @@ -1281,6 +1024,27 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t } } +Arabica::XPath::NodeSet GlobalState::getActiveStates() { + return interpreter->refsToStates(activeStatesRefs); +} + +Arabica::XPath::NodeSet GlobalState::getAlreadyEnteredStates() { + return interpreter->refsToStates(alreadyEnteredStatesRefs); +} + +std::map > GlobalState::getHistoryStates() { + std::map > historyValue; + for (std::map >::iterator histIter = historyStatesRefs.begin(); histIter != historyStatesRefs.end(); histIter++) { + historyValue[histIter->first] = interpreter->refsToStates(histIter->second); + } + return historyValue; +} + + +Arabica::XPath::NodeSet GlobalTransition::getTransitions() const { + return interpreter->refsToTransitions(transitionRefs); +} + std::list GlobalTransition::getCommonEvents(const NodeSet& transitions) { std::list prefixes; std::list longestPrefixes; diff --git a/src/uscxml/transform/ChartToFSM.h b/src/uscxml/transform/ChartToFSM.h index 2f97a24..9e583b1 100644 --- a/src/uscxml/transform/ChartToFSM.h +++ b/src/uscxml/transform/ChartToFSM.h @@ -31,7 +31,33 @@ namespace uscxml { class GlobalState; class GlobalTransition; -class FlatteningInterpreter; +class ChartToFSM; + +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; + } + + static uint64_t stateMachineComplexity(const Arabica::DOM::Element& root); + +protected: + static Complexity calculateStateMachineComplexity(const Arabica::DOM::Element& root); + + uint64_t value; + std::list history; +}; class USCXML_API GlobalState { public: @@ -40,20 +66,25 @@ public: GlobalState(const Arabica::XPath::NodeSet& activeStates, const Arabica::XPath::NodeSet& alreadyEnteredStates, // we need to remember for binding=late const std::map >& historyStates, - const std::string& xmlNSPrefix); - - Arabica::XPath::NodeSet activeStates; - Arabica::XPath::NodeSet alreadyEnteredStates; - std::map > historyStates; - - std::map incoming; - std::map outgoing; + const std::string& xmlNSPrefix, + ChartToFSM* flattener); + + std::set activeStatesRefs; + std::set alreadyEnteredStatesRefs; + std::map > historyStatesRefs; + + Arabica::XPath::NodeSet getActiveStates(); + Arabica::XPath::NodeSet getAlreadyEnteredStates(); + std::map > getHistoryStates(); + + std::list sortedOutgoing; std::string stateId; + std::string activeId; - static int gIndex; - - std::string index; + long index; bool isFinal; + + ChartToFSM* interpreter; }; @@ -70,19 +101,17 @@ public: Arabica::DOM::Element uninvoke; }; - GlobalTransition(const Arabica::XPath::NodeSet& transitions, DataModel dataModel, FlatteningInterpreter* flattener); + GlobalTransition(const Arabica::XPath::NodeSet& transitions, DataModel dataModel, ChartToFSM* flattener); bool isValid; // constructor will determine, calling code will delete if not bool isEventless; // whether or not all our transitions are eventless bool isTargetless; // whether or not all our transitions are eventless bool isSubset; // there is a superset to this set - -// std::vector firstElemPerLevel; -// std::vector nrElemPerLevel; -// std::vector prioPerLevel; - - Arabica::XPath::NodeSet transitions; // constituting transitions - + bool hasExecutableContent; + + std::set transitionRefs; // indizes of constituting transitions + Arabica::XPath::NodeSet getTransitions() const; + std::list eventNames; // the list of longest event names that will enable this set std::string eventDesc; // space-seperated eventnames for convenience std::string condition; // conjunction of all the set's conditions @@ -91,18 +120,12 @@ public: // executable content we gathered when we took the transition std::list actions; - Arabica::XPath::NodeSet entered; - Arabica::XPath::NodeSet exited; - - Arabica::XPath::NodeSet invoke; - Arabica::XPath::NodeSet uninvoke; - std::string transitionId; std::string source; std::string destination; - std::string index; - FlatteningInterpreter* interpreter; + long index; + ChartToFSM* interpreter; bool operator< (const GlobalTransition& other) const; @@ -110,17 +133,23 @@ protected: std::list getCommonEvents(const Arabica::XPath::NodeSet& transitions); }; -class USCXML_API FlatteningInterpreter : public InterpreterRC, public InterpreterMonitor { + +class USCXML_API ChartToFSM : public InterpreterRC, public InterpreterMonitor { public: - FlatteningInterpreter(const Arabica::DOM::Document& doc); - virtual ~FlatteningInterpreter(); + virtual ~ChartToFSM(); + +protected: + ChartToFSM(const Interpreter& other); Arabica::DOM::Document getDocument() const; // overwrite to return flat FSM InterpreterState interpret(); + + Arabica::XPath::NodeSet refsToStates(const std::set&); + Arabica::XPath::NodeSet refsToTransitions(const std::set&); + + std::vector > indexedTransitions; + std::vector > indexedStates; - std::list > indexedTransitions; - -protected: // gather executable content per microstep void executeContent(const Arabica::DOM::Element& content, bool rethrow = false); @@ -140,61 +169,38 @@ protected: virtual void beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing); void explode(); + void getPotentialTransitionsForConf(const Arabica::XPath::NodeSet& conf, std::map& outMap); void labelTransitions(); -// void weightTransitions(); - void createDocument(); void indexTransitions(const Arabica::DOM::Element& root); std::list sortTransitions(std::list list); - void appendGlobalStateNode(GlobalState* globalState); - Arabica::DOM::Node globalTransitionToNode(GlobalTransition* globalTransition); - - GlobalState* _start; - GlobalTransition* _currGlobalTransition; - - uint64_t _perfProcessed; - uint64_t _perfTotal; + // we need this static as we use it in a sort function + static std::map, Arabica::DOM::Node > _transParents; + + static bool filterSameState(const Arabica::XPath::NodeSet& transitions); + + uint64_t _perfTransProcessed; + uint64_t _perfTransTotal; + uint64_t _perfStatesProcessed; + uint64_t _perfStatesSkippedProcessed; + uint64_t _perfStatesSkippedTotal; + uint64_t _perfStatesCachedProcessed; + uint64_t _perfStatesCachedTotal; uint64_t _lastTimeStamp; - int maxDepth; - int maxOrder; - size_t _lastTransientStateId; + size_t _lastStateIndex; + size_t _lastTransIndex; + GlobalState* _start; + GlobalTransition* _currGlobalTransition; Arabica::DOM::Document _flatDoc; std::map _globalConf; -}; - -class USCXML_API ChartToFSM { -public: - static Interpreter flatten(const Interpreter& other); - static uint64_t stateMachineComplexity(const Arabica::DOM::Element& 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 history; - }; - - static Complexity calculateStateMachineComplexity(const Arabica::DOM::Element& root); - + std::map _transPerActiveConf; // potentially enabled transition sets per active configuration + + friend class GlobalTransition; + friend class GlobalState; }; } diff --git a/src/uscxml/transform/ChartToFlatSCXML.cpp b/src/uscxml/transform/ChartToFlatSCXML.cpp new file mode 100644 index 0000000..f279a67 --- /dev/null +++ b/src/uscxml/transform/ChartToFlatSCXML.cpp @@ -0,0 +1,351 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "ChartToFlatSCXML.h" +#include "uscxml/transform/FlatStateIdentifier.h" + +#define CREATE_TRANSIENT_STATE_WITH_CHILDS(stateId) \ +if (childs.size() > 0) { \ + Element transientState = _flatDoc.createElementNS(_nsInfo.nsURL, "state"); \ + _nsInfo.setPrefix(transientState);\ + transientState.setAttribute("transient", "true"); \ + if (stateId.length() > 0) \ + transientState.setAttribute("id", stateId); \ + for (int i = 0; i < childs.size(); i++) { \ + Node imported = _flatDoc.importNode(childs[i], true); \ + transientState.appendChild(imported); \ + } \ + transientStateChain.push_back(transientState); \ +} \ +childs = NodeSet(); + +namespace uscxml { + +using namespace Arabica::DOM; +using namespace Arabica::XPath; + +ChartToFlatSCXML::operator Interpreter() { + if (!HAS_ATTR(_scxml, "flat") || !DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + createDocument(); + } + return Interpreter::fromClone(shared_from_this()); +} + +Transformer ChartToFlatSCXML::transform(const Interpreter& other) { + return boost::shared_ptr(new ChartToFlatSCXML(other)); +} + +void ChartToFlatSCXML::writeTo(std::ostream& stream) { + if (!HAS_ATTR(_scxml, "flat") || !DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + createDocument(); + } + stream << _scxml; +} + +void ChartToFlatSCXML::createDocument() { + + if (HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) + return; + + if (_start == NULL) + interpret(); // only if not already flat! + + Element _origSCXML = _scxml; + + _scxml = _flatDoc.createElementNS(_nsInfo.nsURL, "scxml"); + _nsInfo.setPrefix(_scxml); + + _scxml.setAttribute("flat", "true"); + _flatDoc.appendChild(_scxml); + + if (HAS_ATTR(_origSCXML, "datamodel")) { + _scxml.setAttribute("datamodel", ATTR(_origSCXML, "datamodel")); + } + + if (HAS_ATTR(_origSCXML, "name")) { + _scxml.setAttribute("name", ATTR(_origSCXML, "name")); + } + + if (HAS_ATTR(_origSCXML, "binding")) { + _scxml.setAttribute("binding", ATTR(_origSCXML, "binding")); + } + + _scxml.setAttribute("initial", _start->stateId); + + NodeSet datas; + if (_binding == InterpreterImpl::LATE) { + // with late binding, just copy direct datamodel childs + datas = filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", _origSCXML); + } else { + // with early binding, copy all datamodel elements into scxml element + datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "datamodel", _origSCXML).asNodeSet(); + } + for (int i = 0; i < datas.size(); i++) { + if (isInEmbeddedDocument(datas[i])) + continue; // nested document + Node imported = _flatDoc.importNode(datas[i], true); + _scxml.appendChild(imported); + } + + + NodeSet scripts = filterChildElements(_nsInfo.xmlNSPrefix + "script", _origSCXML); + for (int i = 0; i < scripts.size(); i++) { + Node imported = _flatDoc.importNode(scripts[i], true); + _scxml.appendChild(imported); + } + + NodeSet comments = filterChildType(Node_base::COMMENT_NODE, _origSCXML); + for (int i = 0; i < comments.size(); i++) { + Node imported = _flatDoc.importNode(comments[i], true); + _scxml.appendChild(imported); + } + + std::vector > sortedStates; + sortedStates.insert(sortedStates.begin(), _globalConf.begin(), _globalConf.end()); + std::sort(sortedStates.begin(), sortedStates.end(), sortStatesByIndex); + + // int index = 0; + // for (std::vector >::iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { + // const Element& refTrans = *transIter; + // std::cerr << index++ << ": " << refTrans << std::endl; + // } + // std::cerr << std::endl; + + for (std::vector >::iterator confIter = sortedStates.begin(); + confIter != sortedStates.end(); + confIter++) { + appendGlobalStateNode(confIter->second); + } + + _document = _flatDoc; +} + +void ChartToFlatSCXML::appendGlobalStateNode(GlobalState* globalState) { + Element state = _flatDoc.createElementNS(_nsInfo.nsURL, "state"); + _nsInfo.setPrefix(state); + + state.setAttribute("ref", toStr(globalState->index)); + state.setAttribute("id", globalState->stateId); + + if (globalState->isFinal) + state.setAttribute("final", "true"); + + std::list& transitionList = globalState->sortedOutgoing; + + // apend here, for transient state chains to trail the state + _scxml.appendChild(state); + + size_t index = 0; + for (std::list::iterator outIter = transitionList.begin(); + outIter != transitionList.end(); + outIter++) { +// (*outIter)->index = globalState->index + ":" + toStr(index); + state.appendChild(globalTransitionToNode(*outIter)); + index++; + } +} + +/** + * Creates transient states for executable content as a side-effect + */ +Node ChartToFlatSCXML::globalTransitionToNode(GlobalTransition* globalTransition) { + Element transition = _flatDoc.createElementNS(_nsInfo.nsURL, "transition"); + _nsInfo.setPrefix(transition); + + // transition.setAttribute("ref", globalTransition->index); + +#if 1 + transition.setAttribute("members", globalTransition->members); +#endif + // transition.setAttribute("priority", toStr(globalTransition->priority)); + + if (!globalTransition->isEventless) { + transition.setAttribute("event", globalTransition->eventDesc); + } + + if (globalTransition->condition.size() > 0) { + transition.setAttribute("cond", globalTransition->condition); + } + + if (globalTransition->destination.size() > 0) { + transition.setAttribute("final-target", globalTransition->destination); + } + + NodeSet transientStateChain; + + // current active state set + FlatStateIdentifier flatId(globalTransition->source); + std::list currActiveStates = flatId.getActive(); + + // std::cerr << "From " << globalTransition->source << " to " << globalTransition->destination << ":" << std::endl; + + // gather content for new transient state + NodeSet childs; + // iterate all actions taken during the transition + for (std::list::iterator actionIter = globalTransition->actions.begin(); + actionIter != globalTransition->actions.end(); + actionIter++) { + + if (actionIter->transition) { + // DETAIL_EXEC_CONTENT(transition, actionIter); + + Element onexit = _flatDoc.createElementNS(_nsInfo.nsURL, "onexit"); + _nsInfo.setPrefix(onexit); + Node child = actionIter->transition.getFirstChild(); + while(child) { + Node imported = _flatDoc.importNode(child, true); + onexit.appendChild(imported); + child = child.getNextSibling(); + } + // only append if there is something done + if (onexit.hasChildNodes()) + childs.push_back(onexit); + + continue; + } + + if (actionIter->onExit) { + // DETAIL_EXEC_CONTENT(onExit, actionIter); + + childs.push_back(actionIter->onExit); + continue; + } + + if (actionIter->onEntry) { + // DETAIL_EXEC_CONTENT(onEntry, actionIter); + + childs.push_back(actionIter->onEntry); + continue; + } + + if (actionIter->invoke) { + // DETAIL_EXEC_CONTENT(invoke, actionIter); + if (!globalTransition->isTargetless) { + // CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); + } + Element invokeElem = Element(actionIter->invoke); + invokeElem.setAttribute("persist", "true"); + childs.push_back(invokeElem); + continue; + } + + if (actionIter->uninvoke) { + // DETAIL_EXEC_CONTENT(uninvoke, actionIter); + Element uninvokeElem = _flatDoc.createElementNS(_nsInfo.nsURL, "uninvoke"); + _nsInfo.setPrefix(uninvokeElem); + + if (HAS_ATTR(actionIter->uninvoke, "type")) { + uninvokeElem.setAttribute("type", ATTR(actionIter->uninvoke, "type")); + } + if (HAS_ATTR(actionIter->uninvoke, "typeexpr")) { + uninvokeElem.setAttribute("typeexpr", ATTR(actionIter->uninvoke, "typeexpr")); + } + if (HAS_ATTR(actionIter->uninvoke, "id")) { + uninvokeElem.setAttribute("id", ATTR(actionIter->uninvoke, "id")); + } + if (HAS_ATTR(actionIter->uninvoke, "idlocation")) { + uninvokeElem.setAttribute("idlocation", ATTR(actionIter->uninvoke, "idlocation")); + } + childs.push_back(uninvokeElem); + continue; + } + + if (actionIter->exited) { + // std::cerr << " exited(" << ATTR_CAST(actionIter->exited, "id") << ")"; + currActiveStates.remove(ATTR_CAST(actionIter->exited, "id")); + if (childs.size() > 0) { + CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); // create a new transient state to update its id + } + } + + if (actionIter->entered) { + // std::cerr << " entered(" << ATTR_CAST(actionIter->entered, "id") << ")"; + if (childs.size() > 0) + CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); // create a new transient state to update its id + currActiveStates.push_back(ATTR_CAST(actionIter->entered, "id")); + + // we entered a new child - check if it has a datamodel and we entered for the first time + if (_binding == InterpreterImpl::LATE) { + NodeSet datamodel = filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", actionIter->entered); + if (datamodel.size() > 0 && !isMember(actionIter->entered, _globalConf[globalTransition->source]->getAlreadyEnteredStates())) { + childs.push_back(datamodel); + } + } + } + if (!globalTransition->isTargetless) { + // CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) + } + } + + // std::cerr << std::endl; + + // if (globalTransition->isTargetless) { + // for (int i = 0; i < childs.size(); i++) { + // Node imported = _flatDoc.importNode(childs[i], true); + // transition.appendChild(imported); + // // CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) + // } + // return transition; + // } + + CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) + + if (transientStateChain.size() > 0) { + Element prevExitTransitionElem; + + for (int i = 0; i < transientStateChain.size(); i++) { + Element transientStateElem = Element(transientStateChain[i]); + transientStateElem.setAttribute("id", transientStateElem.getAttribute("id") + "-via-" + toStr(_lastTransientStateId++)); + + Element exitTransition = _flatDoc.createElementNS(_nsInfo.nsURL, "transition"); + _nsInfo.setPrefix(exitTransition); + + if (prevExitTransitionElem) { + // point previous to this one + prevExitTransitionElem.setAttribute("target", transientStateElem.getAttribute("id")); + } else { + // update globalTransition->source target + } + + transientStateElem.appendChild(exitTransition); + prevExitTransitionElem = exitTransition; + + if (i == 0) + transition.setAttribute("target", transientStateElem.getAttribute("id")); + + _scxml.appendChild(transientStateElem); + } + + // last one points to actual target + assert(prevExitTransitionElem); + prevExitTransitionElem.setAttribute("target", globalTransition->destination); + + } else { + transition.setAttribute("target", globalTransition->destination); + } + + assert(HAS_ATTR_CAST(transition, "target")); + return transition; +} + +bool ChartToFlatSCXML::sortStatesByIndex(const std::pair& s1, const std::pair& s2) { + return s1.second->index < s2.second->index; +} + +} \ No newline at end of file diff --git a/src/uscxml/transform/ChartToFlatSCXML.h b/src/uscxml/transform/ChartToFlatSCXML.h new file mode 100644 index 0000000..b6dd616 --- /dev/null +++ b/src/uscxml/transform/ChartToFlatSCXML.h @@ -0,0 +1,52 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef FSMTOSCXML_H_DC0B5E09 +#define FSMTOSCXML_H_DC0B5E09 + +#include "ChartToFSM.h" +#include "Transformer.h" + +namespace uscxml { + +class USCXML_API ChartToFlatSCXML : public TransformerImpl, public ChartToFSM { +public: + virtual ~ChartToFlatSCXML() {} + static Transformer transform(const Interpreter& other); + + operator Interpreter(); + + Arabica::DOM::Document getDocument() const { + return _flatDoc; + } + +protected: + void writeTo(std::ostream& stream); + + ChartToFlatSCXML(const Interpreter& other) : TransformerImpl(), ChartToFSM(other) {} + void createDocument(); + + void appendGlobalStateNode(GlobalState* globalState); + Arabica::DOM::Node globalTransitionToNode(GlobalTransition* globalTransition); + static bool sortStatesByIndex(const std::pair& s1, const std::pair& s2); + +}; + +} +#endif /* end of include guard: FSMTOSCXML_H_DC0B5E09 */ diff --git a/src/uscxml/transform/ChartToPromela.cpp b/src/uscxml/transform/ChartToPromela.cpp new file mode 100644 index 0000000..1ae6c8d --- /dev/null +++ b/src/uscxml/transform/ChartToPromela.cpp @@ -0,0 +1,2146 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "uscxml/transform/ChartToFSM.h" +#include "uscxml/transform/ChartToPromela.h" +#include "uscxml/transform/FlatStateIdentifier.h" +#include "uscxml/plugins/datamodel/promela/PromelaParser.h" +#include "uscxml/plugins/datamodel/promela/parser/promela.tab.hpp" + +#include +#include +#include "uscxml/UUID.h" +#include +#include +#include + +#define MSG_QUEUE_LENGTH 5 +#define MAX_MACRO_CHARS 64 +#define MIN_COMMENT_PADDING 60 + +#define BIT_WIDTH(number) (number > 1 ? (int)ceil(log((double)number) / log((double)2.0)) : 1) +#define LENGTH_FOR_NUMBER(input, output) \ +{ \ + int number = input; \ + int output = 0; \ + do { \ + number /= 10; \ + output++; \ + } while (number != 0); \ +} + +#define INDENT_MIN(stream, start, cols) \ +for (int indentIndex = start; indentIndex < cols; indentIndex++) \ + stream << " "; + +namespace uscxml { + +using namespace Arabica::DOM; +using namespace Arabica::XPath; + +Transformer ChartToPromela::transform(const Interpreter& other) { + return boost::shared_ptr(new ChartToPromela(other)); +} + +void ChartToPromela::writeTo(std::ostream& stream) { + writeProgram(stream); +} + +void PromelaEventSource::writeStartEventSources(std::ostream& stream, int indent) { + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + std::list::iterator sourceIter = eventSources.inlines.begin(); + int i = 0; + while(sourceIter != eventSources.inlines.end()) { + if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { + sourceIter++; + continue; + } + std::string sourceName = name + "_"+ toStr(i); + stream << padding << "run " << sourceName << "EventSource();" << std::endl; + + i++; + sourceIter++; + } + +} + +void PromelaEventSource::writeStopEventSources(std::ostream& stream, int indent) { + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + std::list::iterator sourceIter = eventSources.inlines.begin(); + int i = 0; + while(sourceIter != eventSources.inlines.end()) { + if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { + sourceIter++; + continue; + } + std::string sourceName = name + "_"+ toStr(i); + stream << padding << sourceName << "EventSourceDone = 1;" << std::endl; + + i++; + sourceIter++; + } + +} + +void PromelaEventSource::writeDeclarations(std::ostream& stream, int indent) { + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + std::list::iterator sourceIter = eventSources.inlines.begin(); + int i = 0; + while(sourceIter != eventSources.inlines.end()) { + if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { + sourceIter++; + continue; + } + std::string sourceName = name + "_"+ toStr(i); + stream << "bool " << sourceName << "EventSourceDone = 0;" << std::endl; + + i++; + sourceIter++; + } +} + +void PromelaEventSource::writeEventSource(std::ostream& stream) { + + std::list::iterator sourceIter = eventSources.inlines.begin(); + int i = 0; + while(sourceIter != eventSources.inlines.end()) { + if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { + sourceIter++; + continue; + } + + std::string sourceName = name + "_"+ toStr(i); + + stream << "proctype " << sourceName << "EventSource() {" << std::endl; + stream << " " << sourceName << "EventSourceDone = 0;" << std::endl; + stream << " " << sourceName << "NewEvent:" << std::endl; + stream << " " << "if" << std::endl; + stream << " " << ":: " << sourceName << "EventSourceDone -> skip;" << std::endl; + stream << " " << ":: else { " << std::endl; + + Trie& trie = analyzer->getTrie(); + + if (sourceIter->type == PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM) { + std::string content = sourceIter->content; + + boost::replace_all(content, "#REDO#", sourceName + "NewEvent"); + boost::replace_all(content, "#DONE#", sourceName + "Done"); + + std::list eventNames = trie.getChildsWithWords(trie.getNodeWithPrefix("")); + std::list::iterator eventNameIter = eventNames.begin(); + while(eventNameIter != eventNames.end()) { + boost::replace_all(content, "#" + (*eventNameIter)->value + "#", (*eventNameIter)->identifier); + eventNameIter++; + } + + stream << ChartToPromela::beautifyIndentation(content, 2) << std::endl; + + } else { + stream << " " << " if" << std::endl; +// stream << " " << " :: 1 -> " << "goto " << sourceName << "NewEvent;" << std::endl; + + std::list >::const_iterator seqIter = sourceIter->sequences.begin(); + while(seqIter != sourceIter->sequences.end()) { + stream << " " << ":: "; + std::list::const_iterator evIter = seqIter->begin(); + while(evIter != seqIter->end()) { + TrieNode* node = trie.getNodeWithPrefix(*evIter); + stream << "eQ!" << node->identifier << "; "; + evIter++; + } + stream << "goto " << sourceName << "NewEvent;" << std::endl; + seqIter++; + } + + stream << " " << " fi;" << std::endl; + } + + stream << " " << "}" << std::endl; + stream << " " << "fi;" << std::endl; + stream << sourceName << "Done:" << " skip;" << std::endl; + stream << "}" << std::endl; + + i++; + sourceIter++; + } +} + +PromelaEventSource::PromelaEventSource() { + type = PROMELA_EVENT_SOURCE_INVALID; + analyzer = NULL; +} + +PromelaEventSource::PromelaEventSource(const PromelaInlines& sources, const Arabica::DOM::Node& parent) { + type = PROMELA_EVENT_SOURCE_INVALID; + analyzer = NULL; + + eventSources = sources; + container = parent; +} + +void PromelaCodeAnalyzer::addCode(const std::string& code) { + PromelaParser parser(code); + + // find all strings + std::list astNodes; + astNodes.push_back(parser.ast); + + while(astNodes.size() > 0) { + PromelaParserNode* node = astNodes.front(); + astNodes.pop_front(); + + bool hasValue = false; + int assignedValue = 0; + + switch (node->type) { + case PML_STRING: { + std::string unquoted = node->value; + if (boost::starts_with(unquoted, "'")) { + unquoted = unquoted.substr(1, unquoted.size() - 2); + } + addLiteral(unquoted); + break; + } + case PML_ASGN: +// node->dump(); + if (node->operands.back()->type == PML_CONST) { + hasValue = true; + if (isInteger(node->operands.back()->value.c_str(), 10)) { + assignedValue = strTo(node->operands.back()->value); + } + } + if (node->operands.front()->type == PML_CMPND) + node = node->operands.front(); + case PML_CMPND: { + std::string nameOfType; + std::list::iterator opIter = node->operands.begin(); + if ((*opIter)->type != PML_NAME) { + node->dump(); + assert(false); + } + + PromelaTypedef* td = &_typeDefs; + std::string seperator; + + while(opIter != node->operands.end()) { + switch ((*opIter)->type) { + case PML_NAME: + td = &td->types[(*opIter)->value]; + nameOfType += seperator + (*opIter)->value; + if (nameOfType.compare("_x") == 0) + _usesPlatformVars = true; + seperator = "_"; + td->name = nameOfType + "_t"; + break; + case PML_VAR_ARRAY: { + PromelaParserNode* name = (*opIter)->operands.front(); + PromelaParserNode* subscript = *(++(*opIter)->operands.begin()); + td = &td->types[name->value]; + nameOfType += seperator + name->value; + td->name = nameOfType + "_t"; + + if (isInteger(subscript->value.c_str(), 10)) { + td->arraySize = strTo(subscript->value); + } + break; + } + default: + if ((*opIter)->type == PML_CONST) { + // break fall through from ASGN + break; + } + node->dump(); + assert(false); + break; + } + + if (nameOfType.compare("_x_states") == 0) { + _usesInPredicate = true; + } + if (nameOfType.compare("_event_type") == 0) { + addLiteral("internal"); + addLiteral("external"); + addLiteral("platform"); + } + if (nameOfType.compare("_event_origintype") == 0) { + addLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor"); + } + opIter++; + } + + if (hasValue) { + if (td->maxValue < assignedValue) + td->maxValue = assignedValue; + if (td->minValue > assignedValue) + td->minValue = assignedValue; + } + + continue; // skip processing nested AST nodes + } + case PML_NAME: { + _typeDefs.types[node->value].minValue = 0; + _typeDefs.types[node->value].maxValue = 1; + break; + } + + default: +// node->dump(); + break; +// assert(false); + } + + astNodes.merge(node->operands); + } +} + +void PromelaCodeAnalyzer::addEvent(const std::string& eventName) { + if (_events.find(eventName) != _events.end()) + return; + + addLiteral(eventName, _lastEventIndex); + assert(_strIndex.find(eventName) != _strIndex.end()); + + _eventTrie.addWord(eventName); + _events[eventName] = _strIndex[eventName]; + _lastEventIndex++; +} + +void PromelaCodeAnalyzer::addOrigState(const std::string& stateName) { + if (_origStateIndex.find(stateName) == _origStateIndex.end()) { + _origStateIndex[stateName] = _lastStateIndex++; + createMacroName(stateName); + } +} + +void PromelaCodeAnalyzer::addState(const std::string& stateName) { + if (_states.find(stateName) != _states.end()) + return; + + createMacroName(stateName); + +#if 0 +// addLiteral(stateName); +// _states[stateName] = enumerateLiteral(stateName); + if (_origStateMap.find(stateName) == _origStateMap.end()) { + FlatStateIdentifier flatId(stateName); + _origStateMap[stateName] = flatId.getActive(); + for (std::list::iterator origIter = _origStateMap[stateName].begin(); origIter != _origStateMap[stateName].end(); origIter++) { + //addLiteral(*origIter); // add original state names as string literals + if (_origStateIndex.find(*origIter) == _origStateIndex.end()) { + _origStateIndex[*origIter] = _lastStateIndex++; + createMacroName(*origIter); + } + } + } +#endif +} + +#if 0 +int PromelaCodeAnalyzer::arrayIndexForOrigState(const std::string& stateName) { + if (_origStateIndex.find(stateName) == _origStateIndex.end()) + throw std::runtime_error("No original state " + stateName + " known"); + return _origStateIndex[stateName]; +} +#endif + +void PromelaCodeAnalyzer::addLiteral(const std::string& literal, int forceIndex) { + if (boost::starts_with(literal, "'")) + throw std::runtime_error("Literal " + literal + " passed with quotes"); + + if (_strLiterals.find(literal) != _strLiterals.end()) + return; + + _strLiterals.insert(literal); + createMacroName(literal); + enumerateLiteral(literal, forceIndex); +} + +int PromelaCodeAnalyzer::enumerateLiteral(const std::string& literal, int forceIndex) { + if (forceIndex >= 0) { + _strIndex[literal] = forceIndex; + return forceIndex; + } + + if (_strIndex.find(literal) != _strIndex.end()) + return _strIndex[literal]; + + _strIndex[literal] = ++_lastStrIndex; + return _lastStrIndex + 1; +} + +std::string PromelaCodeAnalyzer::createMacroName(const std::string& literal) { + if (_strMacroNames.find(literal) != _strMacroNames.end()) + return _strMacroNames[literal]; + + // find a suitable macro name for the strings + std::string macroName = literal; //literal.substr(1, literal.size() - 2); + + // cannot start with digit + if (isInteger(macroName.substr(0,1).c_str(), 10)) + macroName = "_" + macroName; + + macroName = macroName.substr(0, MAX_MACRO_CHARS); + boost::to_upper(macroName); + + std::string illegalChars = "#\\/:?\"<>| \n\t()[]{}',.-"; + std::string tmp; + std::string::iterator it = macroName.begin(); + while (it < macroName.end()) { + bool found = illegalChars.find(*it) != std::string::npos; + if(found) { + tmp += '_'; + it++; + while(it < macroName.end() && illegalChars.find(*it) != std::string::npos) { + it++; + } + } else { + tmp += *it++; + } + } + macroName = tmp; + + unsigned int index = 2; + while (_macroNameSet.find(macroName) != _macroNameSet.end()) { + std::string suffix = toStr(index); + if (macroName.size() > suffix.size()) { + macroName = macroName.substr(0, macroName.size() - suffix.size()) + suffix; + } else { + macroName = suffix; + } + index++; + } + + _macroNameSet.insert(macroName); + _strMacroNames[literal] = macroName; + return macroName; +} + +std::string PromelaCodeAnalyzer::macroForLiteral(const std::string& literal) { + if (boost::starts_with(literal, "'")) + throw std::runtime_error("Literal " + literal + " passed with quotes"); + + if (_strMacroNames.find(literal) == _strMacroNames.end()) + throw std::runtime_error("No macro for literal " + literal + " known"); + return _strMacroNames[literal]; +} + +int PromelaCodeAnalyzer::indexForLiteral(const std::string& literal) { + if (boost::starts_with(literal, "'")) + throw std::runtime_error("Literal " + literal + " passed with quotes"); + + if (_strIndex.find(literal) == _strIndex.end()) + throw std::runtime_error("No index for literal " + literal + " known"); + return _strIndex[literal]; +} + +std::string PromelaCodeAnalyzer::replaceLiterals(const std::string code) { + std::string replaced = code; + for (std::map::const_iterator litIter = _strMacroNames.begin(); litIter != _strMacroNames.end(); litIter++) { + boost::replace_all(replaced, "'" + litIter->first + "'", litIter->second); + } + return replaced; +} + +std::set PromelaCodeAnalyzer::getEventsWithPrefix(const std::string& prefix) { + std::set eventNames; + std::list trieNodes = _eventTrie.getWordsWithPrefix(prefix); + + std::list::iterator trieIter = trieNodes.begin(); + while(trieIter != trieNodes.end()) { + eventNames.insert((*trieIter)->value); + trieIter++; + } + + return eventNames; +} + + +void ChartToPromela::writeEvents(std::ostream& stream) { + std::map events = _analyzer.getEvents(); + std::map::iterator eventIter = events.begin(); + stream << "/* event name identifiers */" << std::endl; + while(eventIter != events.end()) { + if (eventIter->first.length() > 0) { + stream << "#define " << _analyzer.macroForLiteral(eventIter->first) << " " << _analyzer.indexForLiteral(eventIter->first); + stream << " /* from \"" << eventIter->first << "\" */" << std::endl; + } + eventIter++; + } +} + +void ChartToPromela::writeStates(std::ostream& stream) { + stream << "/* state name identifiers */" << std::endl; + + std::map::iterator stateIter = _globalConf.begin(); + while(stateIter != _globalConf.end()) { + stream << "#define " << "s" << stateIter->second->index << " " << stateIter->second->index; + stream << " /* from \"" << stateIter->first << "\" */" << std::endl; + stateIter++; + } + +// for (int i = 0; i < _globalConf.size(); i++) { +// stream << "#define " << "s" << i << " " << i; +// stream << " /* from \"" << ATTR_CAST(_globalStates[i], "id") << "\" */" << std::endl; +// } +} + +void ChartToPromela::writeStateMap(std::ostream& stream) { + stream << "/* macros for original state names */" << std::endl; + std::map origStates = _analyzer.getOrigStates(); + for (std::map::iterator origIter = origStates.begin(); origIter != origStates.end(); origIter++) { + stream << "#define " << _analyzer.macroForLiteral(origIter->first) << " " << origIter->second; + stream << " /* from \"" << origIter->first << "\" */" << std::endl; + } + +// std::map states = _analyzer.getStates(); +// size_t stateIndex = 0; +// for (std::map::iterator stateIter = states.begin(); stateIter != states.end(); stateIter++) { +// stream << "_x" +// std::list origStates = _analyzer.getOrigState(stateIter->first); +// size_t origIndex = 0; +// for (std::list::iterator origIter = origStates.begin(); origIter != origStates.end(); origIter++) { +// +// } +// } +} + +void ChartToPromela::writeTypeDefs(std::ostream& stream) { + stream << "/* typedefs */" << std::endl; + PromelaCodeAnalyzer::PromelaTypedef typeDefs = _analyzer.getTypes(); + if (typeDefs.types.size() == 0) + return; + + std::list individualDefs; + std::list currDefs; + currDefs.push_back(typeDefs); + + while(currDefs.size() > 0) { + if (std::find(individualDefs.begin(), individualDefs.end(), currDefs.front()) == individualDefs.end()) { + individualDefs.push_back(currDefs.front()); + for (std::map::iterator typeIter = currDefs.front().types.begin(); typeIter != currDefs.front().types.end(); typeIter++) { + currDefs.push_back(typeIter->second); + } + } + currDefs.pop_front(); + } + individualDefs.pop_front(); + + for (std::list::reverse_iterator rIter = individualDefs.rbegin(); rIter != individualDefs.rend(); rIter++) { + PromelaCodeAnalyzer::PromelaTypedef currDef = *rIter; + if (currDef.types.size() == 0 || currDef.name.size() == 0) + continue; + + stream << "typedef " << currDef.name << " {" << std::endl; + if (currDef.name.compare("_event_t") == 0 && currDef.types.find("name") == currDef.types.end()) { // special treatment for _event + stream << " int name;" << std::endl; + } + for (std::map::iterator tIter = currDef.types.begin(); tIter != currDef.types.end(); tIter++) { + if (currDef.name.compare("_x_t") == 0 && tIter->first.compare("states") == 0) { + stream << " bool states[" << _analyzer.getOrigStates().size() << "];" << std::endl; + continue; + } + if (tIter->second.types.size() == 0) { + stream << " " << declForRange(tIter->first, tIter->second.minValue, tIter->second.maxValue, true) << ";" << std::endl; // not further nested +// stream << " int " << tIter->first << ";" << std::endl; // not further nested + } else { + stream << " " << tIter->second.name << " " << tIter->first << ";" << std::endl; + } + } + stream << "};" << std::endl << std::endl; + } + +// stream << "/* typedef instances */" << std::endl; +// PromelaCodeAnalyzer::PromelaTypedef allTypes = _analyzer.getTypes(); +// std::map::iterator typeIter = allTypes.types.begin(); +// while(typeIter != allTypes.types.end()) { +// if (typeIter->second.types.size() > 0) { +// // an actual typedef +// stream << "hidden " << typeIter->second.name << " " << typeIter->first << ";" << std::endl; +// } else { +// stream << "hidden " << declForRange(typeIter->first, typeIter->second.minValue, typeIter->second.maxValue) << ";" << std::endl; +// } +// typeIter++; +// } + +} + +std::string ChartToPromela::declForRange(const std::string& identifier, long minValue, long maxValue, bool nativeOnly) { +// return "int " + identifier; // just for testing + + // we know nothing about this type + if (minValue == 0 && maxValue == 0) + return "int " + identifier; + + if (minValue < 0) { + // only short or int for negatives + if (minValue < -32769 || maxValue > 32767) + return "int " + identifier; + return "short " + identifier; + } + + // type is definitely positive + if (nativeOnly) { + if (maxValue > 32767) + return "int " + identifier; + if (maxValue > 255) + return "short " + identifier; + if (maxValue > 1) + return "byte " + identifier; + return "bool " + identifier; + } else { + return "unsigned " + identifier + " : " + toStr(BIT_WIDTH(maxValue)); + } +} + + +#if 0 +Arabica::XPath::NodeSet ChartToPromela::getTransientContent(const Arabica::DOM::Element& state, const std::string& source) { + Arabica::XPath::NodeSet content; + Arabica::DOM::Element currState = state; + FlatStateIdentifier prevFlatId(source); + for (;;) { + if (_analyzer.usesInPredicate()) { + // insert state assignments into executable content + + std::stringstream stateSetPromela; + stateSetPromela << "#promela-inline " << std::endl; + FlatStateIdentifier currFlatId(ATTR(currState, "id")); + stateSetPromela << " /* from " << prevFlatId.getFlatActive() << " to " << currFlatId.getFlatActive() << " */" << std::endl; + + // add all that are missing from prevFlatId + std::map allOrigStates = _analyzer.getOrigStates(); + for (std::map::iterator allOrigIter = allOrigStates.begin(); allOrigIter != allOrigStates.end(); allOrigIter++) { + if (std::find(currFlatId.getActive().begin(), currFlatId.getActive().end(), allOrigIter->first) != currFlatId.getActive().end() && + std::find(prevFlatId.getActive().begin(), prevFlatId.getActive().end(), allOrigIter->first) == prevFlatId.getActive().end()) { + // active now but not previously + stateSetPromela << " _x.states[" << _analyzer.macroForLiteral(allOrigIter->first) << "] = true; " << std::endl; + } else if (std::find(currFlatId.getActive().begin(), currFlatId.getActive().end(), allOrigIter->first) == currFlatId.getActive().end() && + std::find(prevFlatId.getActive().begin(), prevFlatId.getActive().end(), allOrigIter->first) != prevFlatId.getActive().end()) { + // previously active but not now + stateSetPromela << " _x.states[" << _analyzer.macroForLiteral(allOrigIter->first) << "] = false; " << std::endl; + } + } + Comment comment = _document.createComment(stateSetPromela.str()); + _document.importNode(comment, true); + currState.insertBefore(comment, currState.getFirstChild()); + prevFlatId = currFlatId; + } + + content.push_back(filterChildType(Node_base::COMMENT_NODE, currState)); + if (_analyzer.usesInPredicate()) assert(content.size() > 0); + + if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) { + // breaking here causes final state assignment to be written + 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 transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR_CAST(transitions[0], "target")]; + } + return content; +} +#endif + +#if 0 +Node ChartToPromela::getUltimateTarget(const Arabica::DOM::Element& transition) { + if (!HAS_ATTR(transition, "target")) { + return transition.getParentNode(); + } + + Arabica::DOM::Element currState = _states[ATTR_CAST(transition, "target")]; + + for (;;) { + if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) + return currState; + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR_CAST(transitions[0], "target")]; + } +} +#endif + +void ChartToPromela::writeInlineComment(std::ostream& stream, const Arabica::DOM::Node& 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 ChartToPromela::writeTransition(std::ostream& stream, const GlobalTransition* transition, int indent) { + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + stream << "t" << transition->index << ":"; + int digits = 0; + LENGTH_FOR_NUMBER(transition->index, digits); + + INDENT_MIN(stream, 2 + digits, MIN_COMMENT_PADDING); + stream << " /* from state " << transition->source << " */" << std::endl; + + stream << padding << "atomic {" << std::endl; + indent++; + + for (std::list::const_iterator actionIter = transition->actions.begin(); actionIter != transition->actions.end(); actionIter++) { + if (actionIter->transition) { + // this is executable content from a transition + writeExecutableContent(stream, actionIter->transition, indent); + continue; + } + + if (actionIter->onExit) { + // executable content from an onexit element + writeExecutableContent(stream, actionIter->onExit, indent); + continue; + } + + if (actionIter->onEntry) { + // executable content from an onentry element + writeExecutableContent(stream, actionIter->onEntry, indent); + continue; + } + + if (actionIter->invoke) { + // an invoke element + continue; + } + + if (actionIter->uninvoke) { + // an invoke element to uninvoke + continue; + } + + if (actionIter->exited) { + // we left a state + if (_analyzer.usesInPredicate()) { + stream << padding << "_x.states[" << _analyzer.macroForLiteral(ATTR(actionIter->exited, "id")) << "] = false; " << std::endl; + } + continue; + } + + if (actionIter->entered) { + // we entered a state + if (_analyzer.usesInPredicate()) { + stream << padding << "_x.states[" << _analyzer.macroForLiteral(ATTR(actionIter->entered, "id")) << "] = true; " << std::endl; + } + continue; + } + } + + GlobalState* newState = _globalConf[transition->destination]; + assert(newState != NULL); + + stream << padding << " s = s" << newState->index << ";"; + LENGTH_FOR_NUMBER(newState->index, digits); + INDENT_MIN(stream, 10 + digits, MIN_COMMENT_PADDING); + + stream << " /* to state " << transition->destination << " */" << std::endl; + + + if (newState->isFinal) { + stream << padding << " goto terminate;"; + INDENT_MIN(stream, padding.length() + 14, MIN_COMMENT_PADDING); + stream << "/* final state */" << std::endl; + } else if (transition->isEventless) { + stream << padding << " goto nextTransition;"; + INDENT_MIN(stream, padding.length() + 19, MIN_COMMENT_PADDING); + stream << "/* spontaneous transition, check for more transitions */" << std::endl; + } else { + stream << padding << " eventLess = true;" << std::endl; + stream << padding << " goto nextTransition;"; + INDENT_MIN(stream, padding.length() + 21, MIN_COMMENT_PADDING); + stream << "/* ordinary transition, check for spontaneous transitions */" << std::endl; + } + stream << padding << "}" << std::endl; + +} + +void ChartToPromela::writeExecutableContent(std::ostream& stream, const Arabica::DOM::Node& node, int indent) { + if (!node) + return; + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + if (node.getNodeType() == Node_base::COMMENT_NODE) { + // we cannot have labels in an atomic block, just process inline promela + PromelaInlines promInls = getInlinePromela(boost::trim_copy(node.getNodeValue())); + // TODO! + // if (promInls) { + // stream << padding << "skip;" << std::endl; + // stream << beautifyIndentation(inlinePromela.str(), indent) << std::endl; + // } + } + + if (node.getNodeType() == Node_base::TEXT_NODE) { + if (boost::trim_copy(node.getNodeValue()).length() > 0) + stream << beautifyIndentation(_analyzer.replaceLiterals(node.getNodeValue()), indent) << std::endl; + } + + if (node.getNodeType() != Node_base::ELEMENT_NODE) + return; // skip anything not an element + + Arabica::DOM::Element nodeElem = Arabica::DOM::Element(node); + + if (false) { + } else if(TAGNAME(nodeElem) == "onentry" || TAGNAME(nodeElem) == "onexit" || TAGNAME(nodeElem) == "transition") { + // descent into childs and write their contents + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, child, indent); + child = child.getNextSibling(); + } + } else if(TAGNAME(nodeElem) == "script") { + NodeSet scriptText = filterChildType(Node_base::TEXT_NODE, node, true); + for (int i = 0; i < scriptText.size(); i++) { + stream << _analyzer.replaceLiterals(beautifyIndentation(scriptText[i].getNodeValue(), indent)) << std::endl; + } + + } else if(TAGNAME(nodeElem) == "log") { + std::string label = (HAS_ATTR(nodeElem, "label") ? ATTR(nodeElem, "label") : ""); + std::string expr = (HAS_ATTR(nodeElem, "expr") ? ATTR(nodeElem, "expr") : ""); + std::string trimmedExpr = boost::trim_copy(expr); + bool isStringLiteral = (boost::starts_with(trimmedExpr, "\"") || boost::starts_with(trimmedExpr, "'")); + + std::string formatString; + std::string varString; + std::string seperator; + + if (label.size() > 0) { + formatString += label + ": "; + } + + if (isStringLiteral) { + formatString += expr; + } else { + formatString += "%d"; + varString += seperator + expr; + } + + if (varString.length() > 0) { + stream << padding << "printf(\"" + formatString + "\", " + varString + ");" << std::endl; + } else { + stream << padding << "printf(\"" + formatString + "\");" << std::endl; + } + + } else if(TAGNAME(nodeElem) == "foreach") { + stream << padding << "for (" << (HAS_ATTR(nodeElem, "index") ? ATTR(nodeElem, "index") : "_index") << " in " << ATTR(nodeElem, "array") << ") {" << std::endl; + if (HAS_ATTR(nodeElem, "item")) { + stream << padding << " " << ATTR(nodeElem, "item") << " = " << ATTR(nodeElem, "array") << "[" << (HAS_ATTR(nodeElem, "index") ? ATTR(nodeElem, "index") : "_index") << "];" << std::endl; + } + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, child, indent + 1); + child = child.getNextSibling(); + } + if (HAS_ATTR(nodeElem, "index")) + stream << padding << " " << ATTR(nodeElem, "index") << "++;" << std::endl; + stream << padding << "}" << std::endl; + + } else if(TAGNAME(nodeElem) == "if") { + NodeSet 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(nodeElem) == "assign") { + if (HAS_ATTR(nodeElem, "location")) { + stream << padding << ATTR(nodeElem, "location") << " = "; + } + if (HAS_ATTR(nodeElem, "expr")) { + stream << _analyzer.replaceLiterals(ATTR(nodeElem, "expr")) << ";" << std::endl; + } else { + NodeSet assignTexts = filterChildType(Node_base::TEXT_NODE, nodeElem, true); + if (assignTexts.size() > 0) { + stream << _analyzer.replaceLiterals(boost::trim_copy(assignTexts[0].getNodeValue())) << ";" << std::endl; + } + } + } else if(TAGNAME(nodeElem) == "send" || TAGNAME(nodeElem) == "raise") { + std::string targetQueue; + if (TAGNAME(nodeElem) == "raise") { + targetQueue = "iQ!"; + } else if (!HAS_ATTR(nodeElem, "target")) { + targetQueue = "tmpQ!"; + } else if (ATTR(nodeElem, "target").compare("#_internal") == 0) { + targetQueue = "iQ!"; + } + if (targetQueue.length() > 0) { + // this is for our external queue + std::string event; + + if (HAS_ATTR(nodeElem, "event")) { + event = _analyzer.macroForLiteral(ATTR(nodeElem, "event")); + } else if (HAS_ATTR(nodeElem, "eventexpr")) { + event = ATTR(nodeElem, "eventexpr"); + } + if (_analyzer.usesComplexEventStruct()) { + stream << padding << "{" << std::endl; + stream << padding << " _event_t tmpEvent;" << std::endl; + stream << padding << " tmpEvent.name = " << event << ";" << std::endl; + + if (HAS_ATTR(nodeElem, "idlocation")) { + stream << padding << " /* idlocation */" << std::endl; + stream << padding << " _lastSendId = _lastSendId + 1;" << std::endl; + stream << padding << " " << ATTR(nodeElem, "idlocation") << " = _lastSendId;" << std::endl; + stream << padding << " tmpEvent.sendid = _lastSendId;" << std::endl; + stream << padding << " if" << std::endl; + stream << padding << " :: _lastSendId == 2147483647 -> _lastSendId = 0;" << std::endl; + stream << padding << " :: timeout -> skip;" << std::endl; + stream << padding << " fi;" << std::endl; + } else if (HAS_ATTR(nodeElem, "id")) { + stream << padding << " tmpEvent.sendid = " << _analyzer.macroForLiteral(ATTR(nodeElem, "id")) << ";" << std::endl; + } + + if (_analyzer.usesEventField("origintype") && targetQueue.compare("iQ!") != 0) { + stream << padding << " tmpEvent.origintype = " << _analyzer.macroForLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor") << ";" << std::endl; + } + + if (_analyzer.usesEventField("type")) { + std::string eventType = (targetQueue.compare("iQ!") == 0 ? _analyzer.macroForLiteral("internal") : _analyzer.macroForLiteral("external")); + stream << padding << " tmpEvent.type = " << eventType << ";" << std::endl; + } + + NodeSet sendParams = filterChildElements(_nsInfo.xmlNSPrefix + "param", nodeElem); + NodeSet sendContents = filterChildElements(_nsInfo.xmlNSPrefix + "content", nodeElem); + std::string sendNameList = ATTR(nodeElem, "namelist"); + if (sendParams.size() > 0) { + for (int i = 0; i < sendParams.size(); i++) { + Element paramElem = Element(sendParams[i]); + stream << padding << " tmpEvent.data." << ATTR(paramElem, "name") << " = " << ATTR(paramElem, "expr") << ";" << std::endl; + } + } + if (sendNameList.size() > 0) { + std::list nameListIds = tokenizeIdRefs(sendNameList); + std::list::iterator nameIter = nameListIds.begin(); + while(nameIter != nameListIds.end()) { + stream << padding << " tmpEvent.data." << *nameIter << " = " << *nameIter << ";" << std::endl; + nameIter++; + } + } + + if (sendParams.size() == 0 && sendNameList.size() == 0 && sendContents.size() > 0) { + Element contentElem = Element(sendContents[0]); + if (contentElem.hasChildNodes() && contentElem.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { + stream << padding << " tmpEvent.data = " << spaceNormalize(contentElem.getFirstChild().getNodeValue()) << ";" << std::endl; + } else if (HAS_ATTR(contentElem, "expr")) { + stream << padding << " tmpEvent.data = " << _analyzer.replaceLiterals(ATTR(contentElem, "expr")) << ";" << std::endl; + } + } + stream << padding << " " << targetQueue << "tmpEvent;" << std::endl; + stream << padding << "}" << std::endl; + } else { + stream << padding << targetQueue << event << ";" << std::endl; + } + } + } else if(TAGNAME(nodeElem) == "invoke") { + _invokers[ATTR(nodeElem, "invokeid")].writeStartEventSources(stream, indent); + } else if(TAGNAME(nodeElem) == "uninvoke") { + stream << padding << ATTR(nodeElem, "invokeid") << "EventSourceDone" << "= 1;" << std::endl; + } else if(TAGNAME(nodeElem) == "cancel") { + // noop + } else { + std::cerr << "'" << TAGNAME(nodeElem) << "'" << std::endl << nodeElem << std::endl; + assert(false); + } +} + +#if 0 +void ChartToPromela::writeExecutableContent(std::ostream& stream, const Arabica::DOM::Node& node, int indent) { + +// std::cout << std::endl << node << std::endl; + if (!node) + return; + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + +// std::cerr << node << std::endl; + + if (node.getNodeType() == Node_base::COMMENT_NODE) { + // we cannot have labels in an atomic block, just process inline promela + 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; + + Arabica::DOM::Element nodeElem = Arabica::DOM::Element(node); + + if (false) { +// } else if(TAGNAME(nodeElem) == "state") { +// if (HAS_ATTR(nodeElem, "transient") && DOMUtils::attributeIsTrue(ATTR(nodeElem, "transient"))) { +// Arabica::XPath::NodeSet execContent = getTransientContent(nodeElem); +// for (int i = 0; i < execContent.size(); i++) { +// writeExecutableContent(stream, execContent[i], indent); +// } +// } + } else if(TAGNAME(nodeElem) == "transition") { + stream << "t" << _transitions[nodeElem] << ":"; + + int number = _transitions[nodeElem]; + int digits = 0; + do { + number /= 10; + digits++; + } while (number != 0); + + INDENT_MIN(stream, 2 + digits, MIN_COMMENT_PADDING); + + Node source = node.getParentNode(); + stream << " /* from state " << ATTR_CAST(source, "id") << " */" << std::endl; + + // gather all executable content + NodeSet execContent = getTransientContent(_states[ATTR(nodeElem, "target")], ATTR_CAST(source, "id")); + + // check for special promela labels + if (HAS_ATTR(nodeElem, "target")) { + PromelaInlines promInls = getInlinePromela(execContent, true); + + if (promInls.acceptLabels > 0) + stream << padding << "acceptLabelT" << _transitions[nodeElem] << ":" << std::endl; + if (promInls.endLabels > 0) + stream << padding << "endLabelT" << _transitions[nodeElem] << ":" << std::endl; + if (promInls.progressLabels > 0) + stream << padding << "progressLabelT" << _transitions[nodeElem] << ":" << std::endl; + } + + stream << padding << "atomic {" << std::endl; +// writeExecutableContent(stream, _states[ATTR(nodeElem, "target")], indent+1); + for (int i = 0; i < execContent.size(); i++) { + writeExecutableContent(stream, execContent[i], indent+1); + } + stream << padding << " skip;" << std::endl; + + Node newState = getUltimateTarget(nodeElem); + for (int i = 0; i < _globalStates.size(); i++) { + if (newState != _globalStates[i]) + continue; + + std::string stateId = ATTR_CAST(_globalStates[i], "id"); + + stream << padding << " s = s" << i << ";"; + + int number = i; + int digits = 0; + do { + number /= 10; + digits++; + } while (number != 0); + + INDENT_MIN(stream, 10 + digits, MIN_COMMENT_PADDING); + + stream << " /* to state " << stateId << " */" << std::endl; + +// if (_analyzer.usesInPredicate()) { +// FlatStateIdentifier flatId(stateId); +// std::map allOrigStates = _analyzer.getOrigStates(); +// for (std::map::iterator allOrigIter = allOrigStates.begin(); allOrigIter != allOrigStates.end(); allOrigIter++) { +// stream << padding << " _x.states[" << _analyzer.macroForLiteral(allOrigIter->first) << "] = "; +// if (std::find(flatId.getActive().begin(), flatId.getActive().end(), allOrigIter->first) != flatId.getActive().end()) { +// stream << "true;" << std::endl; +// } else { +// stream << "false;" << std::endl; +// } +// } +// } + + } + + stream << padding << "}" << std::endl; + if (isFinal(Element(newState))) { + stream << padding << "goto terminate;"; + INDENT_MIN(stream, padding.length() + 14, MIN_COMMENT_PADDING); + stream << "/* final state */" << std::endl; + } else if (!HAS_ATTR_CAST(node, "event")) { + stream << padding << "goto nextTransition;"; + INDENT_MIN(stream, padding.length() + 19, MIN_COMMENT_PADDING); + stream << "/* spontaneous transition, check for more transitions */" << std::endl; + } else { + stream << padding << "eventLess = true;" << std::endl; + stream << padding << "goto nextTransition;"; + INDENT_MIN(stream, padding.length() + 21, MIN_COMMENT_PADDING); + stream << "/* ordinary transition, check for spontaneous transitions */" << std::endl; + } + + } else if(TAGNAME(nodeElem) == "onentry" || TAGNAME(nodeElem) == "onexit") { + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { +// std::cerr << node << std::endl; + if (child.getNodeType() == Node_base::TEXT_NODE) { + if (boost::trim_copy(child.getNodeValue()).length() > 0) + stream << beautifyIndentation(_analyzer.replaceLiterals(child.getNodeValue()), indent) << std::endl; + } + if (child.getNodeType() == Node_base::ELEMENT_NODE) { + writeExecutableContent(stream, child, indent); + } + child = child.getNextSibling(); + } + + } else if(TAGNAME(nodeElem) == "script") { + NodeSet scriptText = filterChildType(Node_base::TEXT_NODE, node, true); + for (int i = 0; i < scriptText.size(); i++) { + stream << _analyzer.replaceLiterals(beautifyIndentation(scriptText[i].getNodeValue(), indent)) << std::endl; + } + + } else if(TAGNAME(nodeElem) == "log") { + std::string label = (HAS_ATTR(nodeElem, "label") ? ATTR(nodeElem, "label") : ""); + std::string expr = (HAS_ATTR(nodeElem, "expr") ? ATTR(nodeElem, "expr") : ""); + std::string trimmedExpr = boost::trim_copy(expr); + bool isStringLiteral = (boost::starts_with(trimmedExpr, "\"") || boost::starts_with(trimmedExpr, "'")); + + std::string formatString; + std::string varString; + std::string seperator; + + if (label.size() > 0) { + formatString += label + ": "; + } + + if (isStringLiteral) { + formatString += expr; + } else { + formatString += "%d"; + varString += seperator + expr; + } + + if (varString.length() > 0) { + stream << padding << "printf(\"" + formatString + "\", " + varString + ");" << std::endl; + } else { + stream << padding << "printf(\"" + formatString + "\");" << std::endl; + } + + } else if(TAGNAME(nodeElem) == "foreach") { + stream << padding << "for (" << (HAS_ATTR(nodeElem, "index") ? ATTR(nodeElem, "index") : "_index") << " in " << ATTR(nodeElem, "array") << ") {" << std::endl; + if (HAS_ATTR(nodeElem, "item")) { + stream << padding << " " << ATTR(nodeElem, "item") << " = " << ATTR(nodeElem, "array") << "[" << (HAS_ATTR(nodeElem, "index") ? ATTR(nodeElem, "index") : "_index") << "];" << std::endl; + } + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, child, indent + 1); + child = child.getNextSibling(); + } + if (HAS_ATTR(nodeElem, "index")) + stream << padding << " " << ATTR(nodeElem, "index") << "++;" << std::endl; + stream << padding << "}" << std::endl; + + } else if(TAGNAME(nodeElem) == "if") { + NodeSet 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(nodeElem) == "assign") { + if (HAS_ATTR(nodeElem, "location")) { + stream << padding << ATTR(nodeElem, "location") << " = "; + } + if (HAS_ATTR(nodeElem, "expr")) { + stream << _analyzer.replaceLiterals(ATTR(nodeElem, "expr")) << ";" << std::endl; + } else { + NodeSet assignTexts = filterChildType(Node_base::TEXT_NODE, nodeElem, true); + if (assignTexts.size() > 0) { + stream << _analyzer.replaceLiterals(boost::trim_copy(assignTexts[0].getNodeValue())) << ";" << std::endl; + } + } + } else if(TAGNAME(nodeElem) == "send" || TAGNAME(nodeElem) == "raise") { + std::string targetQueue; + if (TAGNAME(nodeElem) == "raise") { + targetQueue = "iQ!"; + } else if (!HAS_ATTR(nodeElem, "target")) { + targetQueue = "tmpQ!"; + } else if (ATTR(nodeElem, "target").compare("#_internal") == 0) { + targetQueue = "iQ!"; + } + if (targetQueue.length() > 0) { + // this is for our external queue + std::string event; + + if (HAS_ATTR(nodeElem, "event")) { + event = _analyzer.macroForLiteral(ATTR(nodeElem, "event")); + } else if (HAS_ATTR(nodeElem, "eventexpr")) { + event = ATTR(nodeElem, "eventexpr"); + } + if (_analyzer.usesComplexEventStruct()) { + stream << padding << "{" << std::endl; + stream << padding << " _event_t tmpEvent;" << std::endl; + stream << padding << " tmpEvent.name = " << event << ";" << std::endl; + + if (HAS_ATTR(nodeElem, "idlocation")) { + stream << padding << " /* idlocation */" << std::endl; + stream << padding << " _lastSendId = _lastSendId + 1;" << std::endl; + stream << padding << " " << ATTR(nodeElem, "idlocation") << " = _lastSendId;" << std::endl; + stream << padding << " tmpEvent.sendid = _lastSendId;" << std::endl; + stream << padding << " if" << std::endl; + stream << padding << " :: _lastSendId == 2147483647 -> _lastSendId = 0;" << std::endl; + stream << padding << " :: timeout -> skip;" << std::endl; + stream << padding << " fi;" << std::endl; + } else if (HAS_ATTR(nodeElem, "id")) { + stream << padding << " tmpEvent.sendid = " << _analyzer.macroForLiteral(ATTR(nodeElem, "id")) << ";" << std::endl; + } + + if (_analyzer.usesEventField("origintype") && targetQueue.compare("iQ!") != 0) { + stream << padding << " tmpEvent.origintype = " << _analyzer.macroForLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor") << ";" << std::endl; + } + + if (_analyzer.usesEventField("type")) { + std::string eventType = (targetQueue.compare("iQ!") == 0 ? _analyzer.macroForLiteral("internal") : _analyzer.macroForLiteral("external")); + stream << padding << " tmpEvent.type = " << eventType << ";" << std::endl; + } + + NodeSet sendParams = filterChildElements(_nsInfo.xmlNSPrefix + "param", nodeElem); + NodeSet sendContents = filterChildElements(_nsInfo.xmlNSPrefix + "content", nodeElem); + std::string sendNameList = ATTR(nodeElem, "namelist"); + if (sendParams.size() > 0) { + for (int i = 0; i < sendParams.size(); i++) { + Element paramElem = Element(sendParams[i]); + stream << padding << " tmpEvent.data." << ATTR(paramElem, "name") << " = " << ATTR(paramElem, "expr") << ";" << std::endl; + } + } + if (sendNameList.size() > 0) { + std::list nameListIds = tokenizeIdRefs(sendNameList); + std::list::iterator nameIter = nameListIds.begin(); + while(nameIter != nameListIds.end()) { + stream << padding << " tmpEvent.data." << *nameIter << " = " << *nameIter << ";" << std::endl; + nameIter++; + } + } + + if (sendParams.size() == 0 && sendNameList.size() == 0 && sendContents.size() > 0) { + Element contentElem = Element(sendContents[0]); + if (contentElem.hasChildNodes() && contentElem.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { + stream << padding << " tmpEvent.data = " << spaceNormalize(contentElem.getFirstChild().getNodeValue()) << ";" << std::endl; + } else if (HAS_ATTR(contentElem, "expr")) { + stream << padding << " tmpEvent.data = " << _analyzer.replaceLiterals(ATTR(contentElem, "expr")) << ";" << std::endl; + } + } + stream << padding << " " << targetQueue << "tmpEvent;" << std::endl; + stream << padding << "}" << std::endl; + } else { + stream << padding << targetQueue << event << ";" << std::endl; + } + } + } else if(TAGNAME(nodeElem) == "invoke") { + _invokers[ATTR(nodeElem, "invokeid")].writeStartEventSources(stream, indent); + } else if(TAGNAME(nodeElem) == "uninvoke") { + stream << padding << ATTR(nodeElem, "invokeid") << "EventSourceDone" << "= 1;" << std::endl; + } else if(TAGNAME(nodeElem) == "cancel") { + // noop + } else { + + std::cerr << "'" << TAGNAME(nodeElem) << "'" << std::endl << nodeElem << std::endl; + assert(false); + } + +} +#endif + +PromelaInlines ChartToPromela::getInlinePromela(const std::string& content) { + PromelaInlines prom; + + std::stringstream ssLine(content); + std::string line; + + bool isInPromelaCode = false; + bool isInPromelaEventSource = false; + PromelaInline promInl; + + while(std::getline(ssLine, line)) { + std::string trimLine = boost::trim_copy(line); + if (trimLine.length() == 0) + continue; + if (boost::starts_with(trimLine, "#promela")) { + if (isInPromelaCode || isInPromelaEventSource) { + prom.inlines.push_back(promInl); + isInPromelaCode = false; + isInPromelaEventSource = false; + } + promInl = PromelaInline(); + } + + if (false) { + } else if (boost::starts_with(trimLine, "#promela-progress")) { + prom.progressLabels++; + promInl.type = PromelaInline::PROMELA_PROGRESS_LABEL; + promInl.content = line; + prom.inlines.push_back(promInl); + } else if (boost::starts_with(trimLine, "#promela-accept")) { + prom.acceptLabels++; + promInl.type = PromelaInline::PROMELA_ACCEPT_LABEL; + promInl.content = line; + prom.inlines.push_back(promInl); + } else if (boost::starts_with(trimLine, "#promela-end")) { + prom.endLabels++; + promInl.type = PromelaInline::PROMELA_END_LABEL; + promInl.content = line; + prom.inlines.push_back(promInl); + } else if (boost::starts_with(trimLine, "#promela-inline")) { + prom.codes++; + isInPromelaCode = true; + promInl.type = PromelaInline::PROMELA_CODE; + } else if (boost::starts_with(trimLine, "#promela-event-source-custom")) { + prom.customEventSources++; + isInPromelaCode = true; + promInl.type = PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM; + } else if (boost::starts_with(trimLine, "#promela-event-source")) { + prom.eventSources++; + isInPromelaEventSource = true; + promInl.type = PromelaInline::PROMELA_EVENT_SOURCE; + } else if (isInPromelaCode) { + promInl.content += line; + promInl.content += "\n"; + } else if (isInPromelaEventSource) { + std::list seq; + std::stringstream ssToken(trimLine); + std::string token; + while(std::getline(ssToken, token, ' ')) { + if (token.length() == 0) + continue; + seq.push_back(token); + } + promInl.sequences.push_back(seq); + } + } + // inline code ends with comment + if (isInPromelaCode || isInPromelaEventSource) { + prom.inlines.push_back(promInl); + } + + return prom; +} + +PromelaInlines ChartToPromela::getInlinePromela(const Arabica::DOM::Node& node) { + if (node.getNodeType() != Node_base::COMMENT_NODE) + return getInlinePromela(std::string()); + return getInlinePromela(node.getNodeValue()); +} + +PromelaInlines ChartToPromela::getInlinePromela(const Arabica::XPath::NodeSet& elements, bool recurse) { + PromelaInlines allPromInls; + + Arabica::XPath::NodeSet comments = filterChildType(Node_base::COMMENT_NODE, elements, recurse); + for (int i = 0; i < comments.size(); i++) { + allPromInls.merge(getInlinePromela(comments[i])); + } + return allPromInls; +} + +void ChartToPromela::writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet& 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_CAST(condChain[1]) == "else") { + nextIsElse = true; + } + } + + Element ifNode = Element(condChain[0]); + + stream << padding << "if" << std::endl; + // we need to nest the elseifs to resolve promela if semantics + stream << padding << ":: (" << _analyzer.replaceLiterals(ATTR(ifNode, "cond")) << ") -> {" << std::endl; + + Arabica::DOM::Node child; + if (TAGNAME(ifNode) == "if") { + child = ifNode.getFirstChild(); + } else { + child = ifNode.getNextSibling(); + } + while(child) { + if (child.getNodeType() == Node_base::ELEMENT_NODE) { + Arabica::DOM::Element childElem = Arabica::DOM::Element(child); + if (TAGNAME(childElem) == "elseif" || TAGNAME_CAST(childElem) == "else") + break; + writeExecutableContent(stream, childElem, indent + 1); + } + child = child.getNextSibling(); + } + stream << padding << "}" << std::endl; + stream << padding << ":: else -> "; + + if (nextIsElse) { + child = condChain[1].getNextSibling(); + stream << "{" << std::endl; + while(child) { + if (child.getNodeType() == Node_base::ELEMENT_NODE) { + 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 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 ChartToPromela::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 ChartToPromela::writeStrings(std::ostream& stream) { + stream << "/* string literals */" << std::endl; + std::set literals = _analyzer.getLiterals(); + std::map events = _analyzer.getEvents(); + std::map origStates = _analyzer.getOrigStates(); + + for (std::set::const_iterator litIter = literals.begin(); litIter != literals.end(); litIter++) { + if (events.find(*litIter) == events.end() && (origStates.find(*litIter) == origStates.end() || !_analyzer.usesInPredicate())) + stream << "#define " << _analyzer.macroForLiteral(*litIter) << " " << _analyzer.indexForLiteral(*litIter) << " /* " << *litIter << " */" << std::endl; + } +} + +void ChartToPromela::writeDeclarations(std::ostream& stream) { + + stream << "/* global variables */" << std::endl; + + if (_analyzer.usesComplexEventStruct()) { + // event is defined with the typedefs + stream << "_event_t _event; /* current state */" << std::endl; + stream << "unsigned s : " << BIT_WIDTH(_globalConf.size() + 1) << "; /* current state */" << std::endl; + stream << "chan iQ = [" << MSG_QUEUE_LENGTH << "] of {_event_t} /* internal queue */" << std::endl; + stream << "chan eQ = [" << MSG_QUEUE_LENGTH << "] of {_event_t} /* external queue */" << std::endl; + stream << "chan tmpQ = [" << MSG_QUEUE_LENGTH << "] of {_event_t} /* temporary queue for external events in transitions */" << std::endl; + stream << "hidden _event_t tmpQItem;" << std::endl; + } else { + stream << "unsigned _event : " << BIT_WIDTH(_analyzer.getEvents().size() + 1) << "; /* current event */" << std::endl; + stream << "unsigned s : " << BIT_WIDTH(_globalConf.size() + 1) << "; /* current state */" << std::endl; + stream << "chan iQ = [" << MSG_QUEUE_LENGTH << "] of {int} /* internal queue */" << std::endl; + stream << "chan eQ = [" << MSG_QUEUE_LENGTH << "] of {int} /* external queue */" << std::endl; + stream << "chan tmpQ = [" << MSG_QUEUE_LENGTH << "] of {int} /* temporary queue for external events in transitions */" << std::endl; + stream << "hidden unsigned tmpQItem : " << BIT_WIDTH(_analyzer.getEvents().size() + 1) << ";" << std::endl; + } + stream << "bool eventLess = true; /* whether to process event-less only n this step */" << std::endl; + stream << "hidden int _index; /* helper for indexless foreach loops */" << std::endl; + + if (_analyzer.getTypes().types.find("_ioprocessors") != _analyzer.getTypes().types.end()) { + stream << "hidden _ioprocessors_t _ioprocessors;" << std::endl; + } + + if (_analyzer.usesEventField("sendid")) { + stream << "hidden int _lastSendId = 0; /* sequential counter for send ids */"; + } + +// if (_analyzer.usesPlatformVars()) { +// stream << "_x_t _x;" << std::endl; +// } + + stream << std::endl; + + // get all data elements + NodeSet datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); + // NodeSet dataText = filterChildType(Node_base::TEXT_NODE, datas, true); + + // write their text content + stream << "/* datamodel variables */" << std::endl; + std::set processedIdentifiers; + for (int i = 0; i < datas.size(); i++) { + + Node data = datas[i]; + if (isInEmbeddedDocument(data)) + continue; + + std::string identifier = (HAS_ATTR_CAST(data, "id") ? ATTR_CAST(data, "id") : ""); + std::string expression = (HAS_ATTR_CAST(data, "expr") ? ATTR_CAST(data, "expr") : ""); + std::string type = boost::trim_copy(HAS_ATTR_CAST(data, "type") ? ATTR_CAST(data, "type") : ""); + + if (processedIdentifiers.find(identifier) != processedIdentifiers.end()) + continue; + processedIdentifiers.insert(identifier); + + if (boost::starts_with(type, "string")) { + type = "int" + type.substr(6, type.length() - 6); + } + std::string arrSize; + + NodeSet dataText = filterChildType(Node_base::TEXT_NODE, data, true); + std::string value; + if (dataText.size() > 0) { + value = dataText[0].getNodeValue(); + boost::trim(value); + } + + if (identifier.length() > 0) { + + size_t bracketPos = type.find("["); + if (bracketPos != std::string::npos) { + arrSize = type.substr(bracketPos, type.length() - bracketPos); + type = type.substr(0, bracketPos); + } + std::string decl = type + " " + identifier + arrSize; + + if (arrSize.length() > 0) { + stream << decl << ";" << std::endl; + _varInitializers.push_back(value); + } else { + stream << decl; + if (expression.length() > 0) { + // id and expr given + stream << " = " << _analyzer.replaceLiterals(boost::trim_copy(expression)) << ";" << std::endl; + } else if (value.length() > 0) { + // id and text content given + stream << " = " << _analyzer.replaceLiterals(value) << ";" << std::endl; + } else { + // only id given + stream << ";" << std::endl; + } + } + } else if (value.length() > 0) { + // no id but text content given + stream << beautifyIndentation(value) << std::endl; + } + } + + PromelaCodeAnalyzer::PromelaTypedef allTypes = _analyzer.getTypes(); + std::map::iterator typeIter = allTypes.types.begin(); + while(typeIter != allTypes.types.end()) { + if (processedIdentifiers.find(typeIter->first) != processedIdentifiers.end()) { + typeIter++; + continue; + } + if (typeIter->first == "_event" || typeIter->first == "_ioprocessors" || typeIter->first == "_SESSIONID" || typeIter->first == "_NAME") { + typeIter++; + continue; + } + + processedIdentifiers.insert(typeIter->first); + + if (typeIter->second.types.size() == 0) { + stream << "hidden " << declForRange(typeIter->first, typeIter->second.minValue, typeIter->second.maxValue) << ";" << std::endl; + } else { + stream << "hidden " << typeIter->second.name << " " << typeIter->first << ";" << std::endl; + } + typeIter++; + } + + stream << std::endl; + stream << "/* event sources */" << std::endl; + + if (_globalEventSource) { + _globalEventSource.writeDeclarations(stream); + } + + std::map::iterator invIter = _invokers.begin(); + while(invIter != _invokers.end()) { + invIter->second.writeDeclarations(stream); + invIter++; + } + +} + +void ChartToPromela::writeEventSources(std::ostream& stream) { + std::list::iterator inlineIter; + + if (_globalEventSource) { + _globalEventSource.writeEventSource(stream); + } + + std::map::iterator invIter = _invokers.begin(); + while(invIter != _invokers.end()) { + invIter->second.writeEventSource(stream); + invIter++; + } +} + +void ChartToPromela::writeFSM(std::ostream& stream) { + NodeSet 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; + + assert(_start->sortedOutgoing.size() == 1); + // initial transition has to be first one for control flow at start + writeTransition(stream, _start->sortedOutgoing.front(), 1); + + // every other transition + for (std::map::iterator stateIter = _globalConf.begin(); stateIter != _globalConf.end(); stateIter++) { + for (std::list::iterator transIter = stateIter->second->sortedOutgoing.begin(); transIter != stateIter->second->sortedOutgoing.end(); transIter++) { + // don't write initial transition + if (_start->sortedOutgoing.front() == *transIter) + continue; + // don't write trivial transitions + if ((*transIter)->hasExecutableContent) + writeTransition(stream, *transIter, 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?_event; eQ!_event }" << 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 ? _event /* from internal queue */" << std::endl; + stream << " :: else -> eQ ? _event /* from external queue */" << std::endl; + stream << " fi;" << std::endl << std::endl; + stream << " /* event dispatching per state */" << std::endl; + stream << "nextTransition:" << std::endl; + stream << " if" << std::endl; + + writeEventDispatching(stream); + + stream << " :: else -> assert(false); /* this is an error as we dispatched all valid states */" << std::endl; + stream << " fi;" << std::endl; + stream << "terminate: skip;" << std::endl; + + // stop all event sources + if (_globalEventSource) + _globalEventSource.writeStopEventSources(stream, 1); + + std::map::iterator invIter = _invokers.begin(); + while(invIter != _invokers.end()) { + invIter->second.writeStopEventSources(stream, 1); + invIter++; + } + + stream << "}" << std::endl; +} + +void ChartToPromela::writeEventDispatching(std::ostream& stream) { + for (std::map::iterator stateIter = _globalConf.begin(); stateIter != _globalConf.end(); stateIter++) { +// if (_globalStates[i] == _startState) +// continue; + + const std::string& stateId = stateIter->first; + const GlobalState* state = stateIter->second; + + int digits = 0; + LENGTH_FOR_NUMBER(state->index, digits); + stream << " :: (s == s" << state->index << ") -> {"; + + INDENT_MIN(stream, 18 + digits, MIN_COMMENT_PADDING); + + stream << " /* from state " << stateId << " */" << std::endl; + + writeDispatchingBlock(stream, state->sortedOutgoing, 2); +// stream << " goto nextStep;"; + stream << " }" << std::endl; + } +} + +void ChartToPromela::writeDispatchingBlock(std::ostream& stream, std::list transitions, int indent) { + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + if (transitions.size() == 0) { + stream << padding << "eventLess = false;" << std::endl; + stream << padding << "goto nextStep;"; + INDENT_MIN(stream, padding.size() + 13, MIN_COMMENT_PADDING); + stream << "/* no transition applicable */" << std::endl; + return; + } + + + GlobalTransition* currTrans = transitions.front(); + transitions.pop_front(); + std::stringstream tmpSS; + + tmpSS << padding << "if" << std::endl; + size_t lineStart = tmpSS.tellp(); + + if (currTrans->condition.size() > 0) { + tmpSS << padding << ":: (("; + } else { + tmpSS << padding << ":: ("; + } + + if (currTrans->isEventless) { + tmpSS << "eventLess"; + } else { + std::string eventDescs = currTrans->eventDesc; + + std::list eventNames = tokenizeIdRefs(eventDescs); + std::set eventPrefixes; + std::list::iterator eventNameIter = eventNames.begin(); + while(eventNameIter != eventNames.end()) { + std::string eventDesc = *eventNameIter; + 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.length() > 0) { + std::set tmp = _analyzer.getEventsWithPrefix(*eventNameIter); + eventPrefixes.insert(tmp.begin(), tmp.end()); + } + eventNameIter++; + } + + if (eventPrefixes.size() > 0) { + tmpSS << "!eventLess && "; + } else { + tmpSS << "!eventLess"; + } + + std::string seperator; + std::set::iterator eventIter = eventPrefixes.begin(); + while(eventIter != eventPrefixes.end()) { + if (_analyzer.usesComplexEventStruct()) { + tmpSS << seperator << "_event.name == " << _analyzer.macroForLiteral(*eventIter); + } else { + tmpSS << seperator << "_event == " << _analyzer.macroForLiteral(*eventIter); + } + seperator = " || "; + eventIter++; + } + } + + tmpSS << ")"; + if (currTrans->condition.size() > 0) { + tmpSS << " && " + _analyzer.replaceLiterals(currTrans->condition) + ")"; + } + if (currTrans->hasExecutableContent) { + tmpSS << " -> goto t" << currTrans->index << ";"; + size_t lineEnd = tmpSS.tellp(); + size_t lineLength = lineEnd - lineStart; + + for (int i = lineLength; i < MIN_COMMENT_PADDING; i++) + tmpSS << " "; + + tmpSS << " /* transition to " << currTrans->destination << " */" << std::endl; + } else { + + tmpSS << " -> {" << std::endl; + GlobalState* newState = _globalConf[currTrans->destination]; + assert(newState != NULL); + tmpSS << padding << " s = s" << newState->index << ";" << std::endl; + + if (newState->isFinal) { + tmpSS << padding << " goto terminate;"; + INDENT_MIN(tmpSS, padding.length() + 14, MIN_COMMENT_PADDING); + tmpSS << "/* final state */" << std::endl; + } else if (currTrans->isEventless) { + tmpSS << padding << " goto nextTransition;"; + INDENT_MIN(tmpSS, padding.length() + 19, MIN_COMMENT_PADDING); + tmpSS << "/* spontaneous transition, check for more transitions */" << std::endl; + } else { + tmpSS << padding << " eventLess = true;" << std::endl; + tmpSS << padding << " goto nextTransition;"; + INDENT_MIN(tmpSS, padding.length() + 21, MIN_COMMENT_PADDING); + tmpSS << "/* ordinary transition, check for spontaneous transitions */" << std::endl; + } + + tmpSS << padding << "}" << std::endl; + } + stream << tmpSS.str(); + + stream << padding << ":: else {" << std::endl; + + writeDispatchingBlock(stream, transitions, indent + 1); + + stream << padding << "}" << std::endl; + stream << padding << "fi;" << std::endl; +} + +void ChartToPromela::writeMain(std::ostream& stream) { + stream << std::endl; + stream << "init {" << std::endl; + if (_varInitializers.size() > 0) { + std::list::iterator initIter = _varInitializers.begin(); + while(initIter != _varInitializers.end()) { + stream << beautifyIndentation(*initIter); + initIter++; + } + stream << std::endl; + } + if (_globalEventSource) + _globalEventSource.writeStartEventSources(stream, 1); + stream << " run step();" << std::endl; + stream << "}" << std::endl; + +} + + +void ChartToPromela::initNodes() { + + // get all states + NodeSet states = getAllStates(); + for (int i = 0; i < states.size(); i++) { + if (InterpreterImpl::isInEmbeddedDocument(states[i])) + continue; + Element stateElem(states[i]); + _analyzer.addOrigState(ATTR(stateElem, "id")); + if (isCompound(stateElem) || isParallel(stateElem)) { + _analyzer.addEvent("done.state." + ATTR(stateElem, "id")); + } + } + + // initialize event trie with all events that might occur + NodeSet 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_CAST(internalEventNames[i], "event")) { + std::string eventNames = ATTR_CAST(internalEventNames[i], "event"); + std::list events = tokenizeIdRefs(eventNames); + for (std::list::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); + if (eventName.size() > 0) + _analyzer.addEvent(eventName); + } + } + } + + // do we need sendid / invokeid? + { + NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true); + NodeSet sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", _scxml, true); + + for (int i = 0; i < invokes.size(); i++) { + if (HAS_ATTR_CAST(invokes[i], "idlocation")) { + } + } + + for (int i = 0; i < sends.size(); i++) { + if (HAS_ATTR_CAST(sends[i], "idlocation")) { + _analyzer.addCode("_event.sendid"); + } + if (HAS_ATTR_CAST(sends[i], "id")) { + _analyzer.addLiteral(ATTR_CAST(sends[i], "id")); + _analyzer.addCode("_event.sendid"); + } + } + + } + + // external event names from comments + NodeSet promelaEventSourceComments; + NodeSet invokers = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "invoke", _scxml).asNodeSet(); + promelaEventSourceComments.push_back(filterChildType(Node_base::COMMENT_NODE, invokers, false)); // comments in invoke elements + promelaEventSourceComments.push_back(filterChildType(Node_base::COMMENT_NODE, _scxml, false)); // comments in scxml element + + for (int i = 0; i < promelaEventSourceComments.size(); i++) { + PromelaInlines promInls = getInlinePromela(promelaEventSourceComments[i]); + PromelaEventSource promES(promInls, promelaEventSourceComments[i].getParentNode()); + + if (TAGNAME_CAST(promelaEventSourceComments[i].getParentNode()) == "scxml") { + promES.type = PromelaEventSource::PROMELA_EVENT_SOURCE_GLOBAL; + promES.analyzer = &_analyzer; + promES.name = "global"; + _globalEventSource = promES; + } else if (TAGNAME_CAST(promelaEventSourceComments[i].getParentNode()) == "invoke") { + if (!HAS_ATTR_CAST(promelaEventSourceComments[i].getParentNode(), "invokeid")) { + Element invoker = Element(promelaEventSourceComments[i].getParentNode()); + invoker.setAttribute("invokeid", "invoker" + toStr(_invokers.size())); + } + std::string invokeId = ATTR_CAST(promelaEventSourceComments[i].getParentNode(), "invokeid"); + promES.type = PromelaEventSource::PROMELA_EVENT_SOURCE_INVOKER; + promES.analyzer = &_analyzer; + promES.name = invokeId; + _invokers[invokeId] = promES; + } + } + +#if 0 + // enumerate transitions + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + int index = 0; + for (int i = 0; i < transitions.size(); i++) { + _transitions[Element(transitions[i])] = index++; + } +#endif + + // add platform variables as string literals + _analyzer.addLiteral("_sessionid"); + _analyzer.addLiteral("_name"); + + if (HAS_ATTR(_scxml, "name")) { + _analyzer.addLiteral(ATTR(_scxml, "name"), _analyzer.indexForLiteral("_sessionid")); + } + + NodeSet contents = filterChildElements(_nsInfo.xmlNSPrefix + "content", _scxml, true); + for (int i = 0; i < contents.size(); i++) { + Element contentElem = Element(contents[i]); + if (contentElem.hasChildNodes() && contentElem.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { + _analyzer.addLiteral(spaceNormalize(contentElem.getFirstChild().getNodeValue())); + } + } + + + // extract and analyze source code + std::set allCode; + std::set allStrings; + { + NodeSet withCond; + withCond.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true)); + withCond.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "if", _scxml, true)); + withCond.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "elseif", _scxml, true)); + for (int i = 0; i < withCond.size(); i++) { + Element elem = Element(withCond[i]); + if (HAS_ATTR(elem, "cond")) { + std::string code = ATTR(elem, "cond"); + code = sanitizeCode(code); + elem.setAttribute("cond", code); + allCode.insert(code); + } + } + } + { + NodeSet withExpr; + withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "log", _scxml, true)); + withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "data", _scxml, true)); + withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "assign", _scxml, true)); + withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "content", _scxml, true)); + withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "param", _scxml, true)); + for (int i = 0; i < withExpr.size(); i++) { + Element elem = Element(withExpr[i]); + if (HAS_ATTR(elem, "expr")) { + std::string code = ATTR(elem, "expr"); + code = sanitizeCode(code); + elem.setAttribute("expr", code); + allCode.insert(code); + } + } + } + { + NodeSet withLocation; + withLocation.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "assign", _scxml, true)); + for (int i = 0; i < withLocation.size(); i++) { + Element elem = Element(withLocation[i]); + if (HAS_ATTR(elem, "location")) { + std::string code = ATTR(elem, "location"); + code = sanitizeCode(code); + elem.setAttribute("location", code); + allCode.insert(code); + } + } + } + { + NodeSet withText; + withText.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml, true)); + withText.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "data", _scxml, true)); + for (int i = 0; i < withText.size(); i++) { + NodeSet texts = filterChildType(Node_base::TEXT_NODE, withText[i], true); + for (int j = 0; j < texts.size(); j++) { + if (texts[j].getNodeValue().size() > 0) { + Text elem = Text(texts[j]); + std::string code = elem.getNodeValue(); + code = sanitizeCode(code); + elem.setNodeValue(code); + allCode.insert(code); + } + } + } + } + for (std::set::const_iterator codeIter = allCode.begin(); codeIter != allCode.end(); codeIter++) { + _analyzer.addCode(*codeIter); + } + +} + +std::string ChartToPromela::sanitizeCode(const std::string& code) { + std::string replaced = code; + boost::replace_all(replaced, "\"", "'"); + boost::replace_all(replaced, "_sessionid", "_SESSIONID"); + boost::replace_all(replaced, "_name", "_NAME"); + return replaced; +} + +void PromelaInline::dump() { + std::list >::iterator outerIter = sequences.begin(); + while(outerIter != sequences.end()) { + std::list::iterator innerIter = outerIter->begin(); + while(innerIter != outerIter->end()) { + std::cout << *innerIter << " "; + innerIter++; + } + std::cout << std::endl; + outerIter++; + } +} + +void ChartToPromela::writeProgram(std::ostream& stream) { + + if (!HAS_ATTR(_scxml, "datamodel") || ATTR(_scxml, "datamodel") != "promela") { + LOG(ERROR) << "Can only convert SCXML documents with \"promela\" datamodel"; + return; + } + + if (_start == NULL) { + interpret(); + } + + if (HAS_ATTR(_scxml, "binding") && ATTR(_scxml, "binding") != "early") { + LOG(ERROR) << "Can only convert for early data bindings"; + return; + } + +// std::cerr << _scxml << std::endl; + + initNodes(); + + writeEvents(stream); + stream << std::endl; + writeStates(stream); + stream << std::endl; + if (_analyzer.usesInPredicate()) { + writeStateMap(stream); + stream << std::endl; + } + writeTypeDefs(stream); + stream << std::endl; + writeStrings(stream); + stream << std::endl; + writeDeclarations(stream); + stream << std::endl; + writeEventSources(stream); + stream << std::endl; + writeFSM(stream); + stream << std::endl; + writeMain(stream); + stream << std::endl; + + // write ltl expression for success + std::stringstream acceptingStates; + std::string seperator; + + for (std::map::iterator stateIter = _globalConf.begin(); stateIter != _globalConf.end(); stateIter++) { + FlatStateIdentifier flatId(stateIter->first); + if (std::find(flatId.getActive().begin(), flatId.getActive().end(), "pass") != flatId.getActive().end()) { + acceptingStates << seperator << "s == s" << stateIter->second->index; + seperator = " || "; + } + } + if (acceptingStates.str().size() > 0) { + stream << "ltl { eventually (" << acceptingStates.str() << ") }" << std::endl; + } + +// if (_states.find("active:{pass}") != _states.end()) { +// for (int i = 0; i < _globalStates.size(); i++) { +// if (_states["active:{pass}"] != _globalStates[i]) +// continue; +// stream << "ltl { eventually (s == s" << i << ") }"; +// break; +// } +// } +} + +} \ No newline at end of file diff --git a/src/uscxml/transform/ChartToPromela.h b/src/uscxml/transform/ChartToPromela.h new file mode 100644 index 0000000..75e3339 --- /dev/null +++ b/src/uscxml/transform/ChartToPromela.h @@ -0,0 +1,279 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef CHARTTOPROMELA_H_RP48RFDJ +#define CHARTTOPROMELA_H_RP48RFDJ + +#include "Transformer.h" +#include "ChartToFSM.h" +#include "uscxml/interpreter/InterpreterDraft6.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/util/Trie.h" + +#include +#include +#include +#include + +namespace uscxml { + +class USCXML_API PromelaInline { +public: + PromelaInline() : type(PROMELA_NIL) {} + + operator bool() { + return (type != PROMELA_NIL); + } + + void dump(); + + enum PromelaInlineType { + PROMELA_NIL, + PROMELA_CODE, + PROMELA_EVENT_SOURCE, + PROMELA_EVENT_SOURCE_CUSTOM, + PROMELA_PROGRESS_LABEL, + PROMELA_ACCEPT_LABEL, + PROMELA_END_LABEL + }; + + std::string content; + std::list > sequences; + + PromelaInlineType type; +}; + +class USCXML_API PromelaInlines { +public: + PromelaInlines() : progressLabels(0), acceptLabels(0), endLabels(0), eventSources(0), customEventSources(0), codes(0) {} + + void merge(const PromelaInlines& other) { + inlines.insert(inlines.end(), other.inlines.begin(), other.inlines.end()); + progressLabels += other.progressLabels; + acceptLabels += other.acceptLabels; + endLabels += other.endLabels; + eventSources += other.eventSources; + customEventSources += other.customEventSources; + codes += other.codes; + } + + operator bool() { + return inlines.size() > 0; + } + + std::list inlines; + int progressLabels; + int acceptLabels; + int endLabels; + int eventSources; + int customEventSources; + int codes; +}; + +class USCXML_API PromelaCodeAnalyzer { +public: + class PromelaTypedef { + public: + PromelaTypedef() : arraySize(0), minValue(0), maxValue(0) {} + std::string name; + std::string type; + size_t arraySize; + size_t minValue; + size_t maxValue; + std::map types; + + bool operator==(const PromelaTypedef& other) const { + return name == other.name; + } + + }; + + PromelaCodeAnalyzer() : _eventTrie("."), _lastStrIndex(1), _lastStateIndex(0), _lastEventIndex(1), _usesInPredicate(false), _usesPlatformVars(false) { + } + + void addCode(const std::string& code); + void addEvent(const std::string& eventName); + void addState(const std::string& stateName); + void addOrigState(const std::string& stateName); + void addLiteral(const std::string& stateName, int forceIndex = -1); + + bool usesComplexEventStruct() { + return _typeDefs.types.find("_event") != _typeDefs.types.end() && _typeDefs.types["_event"].types.size() > 0; + } + bool usesEventField(const std::string& fieldName) { + if (usesComplexEventStruct() && _typeDefs.types["_event"].types.find(fieldName) != _typeDefs.types["_event"].types.end()) + return true; + return false; + } + + bool usesInPredicate() { + return _usesInPredicate; + } + bool usesPlatformVars() { + return _usesPlatformVars; + } + + std::string macroForLiteral(const std::string& literal); + int indexForLiteral(const std::string& literal); + + std::set getLiterals() { + return _strLiterals; + } + std::set getEventsWithPrefix(const std::string& prefix); + std::map& getEvents() { + return _events; + } + + std::map& getStates() { + return _states; + } + + std::map& getOrigStates() { + return _origStateIndex; + } + + + Trie& getTrie() { + return _eventTrie; + } + + std::string replaceLiterals(const std::string code); + + PromelaTypedef& getTypes() { + return _typeDefs; + } + +protected: + std::string createMacroName(const std::string& literal); + int enumerateLiteral(const std::string& literal, int forceIndex = -1); + + std::set _strLiterals; // all string literals + std::map _strMacroNames; // macronames for string literals + std::map _strIndex; // integer enumeration for string + std::map _origStateIndex; // state enumeration for original states + + std::map _states; + std::map _events; + + PromelaTypedef _typeDefs; + + Trie _eventTrie; + +private: + std::set _macroNameSet; // helper set for uniqueness of macros + int _lastStrIndex; + int _lastStateIndex; + int _lastEventIndex; + bool _usesInPredicate; + bool _usesPlatformVars; +}; + +class USCXML_API PromelaEventSource { +public: + + enum PromelaEventSourceType { + PROMELA_EVENT_SOURCE_INVALID, + PROMELA_EVENT_SOURCE_INVOKER, + PROMELA_EVENT_SOURCE_GLOBAL, + }; + + PromelaEventSource(); + PromelaEventSource(const PromelaInlines& sources, const Arabica::DOM::Node& parent); + + void writeStartEventSources(std::ostream& stream, int indent = 0); + void writeStopEventSources(std::ostream& stream, int indent = 0); + void writeDeclarations(std::ostream& stream, int indent = 0); + void writeEventSource(std::ostream& stream); + + operator bool() { + return type != PROMELA_EVENT_SOURCE_INVALID; + } + + std::string name; + PromelaInlines eventSources; + Arabica::DOM::Node container; + PromelaEventSourceType type; + PromelaCodeAnalyzer* analyzer; +}; + +class USCXML_API ChartToPromela : public TransformerImpl, public ChartToFSM { +public: + + virtual ~ChartToPromela() {} + static Transformer transform(const Interpreter& other); + + void writeTo(std::ostream& stream); + +protected: + ChartToPromela(const Interpreter& other) : TransformerImpl(), ChartToFSM(other) {} + + void initNodes(); + + static std::string beautifyIndentation(const std::string& code, int indent = 0); + + void writeProgram(std::ostream& stream); + + void writeEvents(std::ostream& stream); + void writeStates(std::ostream& stream); + void writeStateMap(std::ostream& stream); + void writeTypeDefs(std::ostream& stream); + void writeStrings(std::ostream& stream); + void writeDeclarations(std::ostream& stream); + void writeEventSources(std::ostream& stream); + void writeTransition(std::ostream& stream, const GlobalTransition* transition, int indent = 0); + void writeExecutableContent(std::ostream& stream, const Arabica::DOM::Node& node, int indent = 0); + void writeInlineComment(std::ostream& stream, const Arabica::DOM::Node& 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& condChain, int indent = 0); + void writeDispatchingBlock(std::ostream& stream, std::list, int indent = 0); + + Arabica::XPath::NodeSet getTransientContent(const Arabica::DOM::Element& state, const std::string& source = ""); + //Arabica::DOM::Node getUltimateTarget(const Arabica::DOM::Element& transition); + + static PromelaInlines getInlinePromela(const std::string&); + static PromelaInlines getInlinePromela(const Arabica::XPath::NodeSet& elements, bool recurse = false); + static PromelaInlines getInlinePromela(const Arabica::DOM::Node& elements); + + static std::string declForRange(const std::string& identifier, long minValue, long maxValue, bool nativeOnly = false); + +// std::string replaceStringsInExpression(const std::string& expr); + + std::string sanitizeCode(const std::string& code); + +// Arabica::XPath::NodeSet _globalStates; +// Arabica::DOM::Node _startState; +// std::map > _states; +// std::map, int> _transitions; + + std::list _varInitializers; // pending initializations for arrays + + PromelaCodeAnalyzer _analyzer; + + std::map _invokers; + PromelaEventSource _globalEventSource; + + friend class PromelaEventSource; +}; + +} + +#endif /* end of include guard: CHARTTOPROMELA_H_RP48RFDJ */ diff --git a/src/uscxml/transform/FSMToCPP.cpp b/src/uscxml/transform/FSMToCPP.cpp deleted file mode 100644 index 980c389..0000000 --- a/src/uscxml/transform/FSMToCPP.cpp +++ /dev/null @@ -1,546 +0,0 @@ -/** - * @file - * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see . - * @endcond - */ - -#include "uscxml/transform/ChartToFSM.h" -#include "uscxml/transform/FSMToCPP.h" -#include -#include -#include "uscxml/UUID.h" -#include -#include -#include - -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 eventNames = _eventTrie.getWordsWithPrefix(""); - std::list::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_CAST(_globalStates[i], "id") << "\"" << std::endl; - } - -} - -Arabica::XPath::NodeSet FSMToCPP::getTransientContent(const Arabica::DOM::Element& state) { - Arabica::XPath::NodeSet content; - Arabica::DOM::Element 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 transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); - currState = _states[ATTR_CAST(transitions[0], "target")]; - } - - return content; -} - -Node FSMToCPP::getUltimateTarget(const Arabica::DOM::Element& transition) { - Arabica::DOM::Element currState = _states[ATTR(transition, "target")]; - - for (;;) { - if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) - return currState; - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); - currState = _states[ATTR_CAST(transitions[0], "target")]; - } -} - -void FSMToCPP::writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& 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::Element& 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 execContent = getTransientContent(node); - for (int i = 0; i < execContent.size(); i++) { - writeExecutableContent(stream, Arabica::DOM::Element(execContent[i]), indent); - } - } else { - Arabica::DOM::Node child = node.getFirstChild(); - while(child) { - writeExecutableContent(stream, Arabica::DOM::Element(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 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(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 child = node.getFirstChild(); - while(child) { - writeExecutableContent(stream, Arabica::DOM::Element(child), indent); - child = child.getNextSibling(); - } - - } else if(TAGNAME(node) == "script") { - NodeSet 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 child = node.getFirstChild(); - while(child) { - writeExecutableContent(stream, Arabica::DOM::Element(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 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& 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_CAST(condChain[1]) == "else") { - nextIsElse = true; - } - } - - Node ifNode = condChain[0]; - - stream << padding << "if" << std::endl; - // we need to nest the elseifs to resolve promela if semantics - stream << padding << ":: (" << ATTR_CAST(ifNode, "cond") << ") -> {" << std::endl; - - Arabica::DOM::Node child; - if (TAGNAME_CAST(ifNode) == "if") { - child = ifNode.getFirstChild(); - } else { - child = ifNode.getNextSibling(); - } - while(child) { - if (child.getNodeType() == Node_base::ELEMENT_NODE) { - if (TAGNAME_CAST(child) == "elseif" || TAGNAME_CAST(child) == "else") - break; - } - writeExecutableContent(stream, Arabica::DOM::Element(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, Arabica::DOM::Element(child), indent + 1); - child = child.getNextSibling(); - } - stream << padding << "}" << std::endl; - - } else if (noNext) { - stream << "skip;" << std::endl; - } else { - stream << "{" << std::endl; - - Arabica::XPath::NodeSet 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 datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); - NodeSet 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 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 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, Arabica::DOM::Element(transitions[0]), 1); - - for (int i = 0; i < _globalStates.size(); i++) { - if (_globalStates[i] == _startState) - continue; - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); - for (int j = 0; j < transitions.size(); j++) { - writeExecutableContent(stream, Arabica::DOM::Element(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 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& 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 currTrans = transChain[0]; - std::string eventDesc = ATTR_CAST(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 trieNodes = _eventTrie.getWordsWithPrefix(eventDesc); - - std::list::iterator trieIter = trieNodes.begin(); - while(trieIter != trieNodes.end()) { - stream << " || e == e" << (*trieIter)->identifier; - trieIter++; - } - } - - stream << ") && "; - stream << (HAS_ATTR_CAST(currTrans, "cond") ? ATTR_CAST(currTrans, "cond") : "1"); - stream << ") -> goto t" << _transitions[Arabica::DOM::Element(currTrans)] << ";" << std::endl; - ; - - stream << padding << ":: else {" << std::endl; - - Arabica::XPath::NodeSet 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 states = filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml); - for (int i = 0; i < states.size(); i++) { - Arabica::DOM::Element stateElem = Arabica::DOM::Element(states[i]); - _states[ATTR(stateElem, "id")] = stateElem; - if (HAS_ATTR(stateElem, "transient") && DOMUtils::attributeIsTrue(ATTR(stateElem, "transient"))) - continue; - _globalStates.push_back(states[i]); - } - _startState = _states[ATTR(_scxml, "initial")]; - - // initialize event trie with all events that might occur - NodeSet 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_CAST(internalEventNames[i], "event")) { - std::string eventNames = ATTR_CAST(internalEventNames[i], "event"); - std::list events = tokenizeIdRefs(eventNames); - for (std::list::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 transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); - int index = 0; - for (int i = 0; i < transitions.size(); i++) { - _transitions[Arabica::DOM::Element(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 deleted file mode 100644 index 18df7c1..0000000 --- a/src/uscxml/transform/FSMToCPP.h +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @file - * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see . - * @endcond - */ - -#ifndef FSMTOCPP_H_201672B0 -#define FSMTOCPP_H_201672B0 - -#include "uscxml/interpreter/InterpreterDraft6.h" -#include "uscxml/DOMUtils.h" -#include "uscxml/util/Trie.h" - -#include -#include -#include -#include - -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::Element& node, int indent = 0); - void writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& 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& condChain, int indent = 0); - void writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent = 0); - - Arabica::XPath::NodeSet getTransientContent(const Arabica::DOM::Element& state); - Arabica::DOM::Node getUltimateTarget(const Arabica::DOM::Element& transition); - - Trie _eventTrie; - Arabica::XPath::NodeSet _globalStates; - Arabica::DOM::Element _startState; - std::map > _states; - std::map, int> _transitions; - -}; - -} - -#endif /* end of include guard: FSMTOCPP_H_201672B0 */ diff --git a/src/uscxml/transform/FSMToPromela.cpp b/src/uscxml/transform/FSMToPromela.cpp deleted file mode 100644 index 8c2836f..0000000 --- a/src/uscxml/transform/FSMToPromela.cpp +++ /dev/null @@ -1,1712 +0,0 @@ -/** - * @file - * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see . - * @endcond - */ - -#include "uscxml/transform/ChartToFSM.h" -#include "uscxml/transform/FSMToPromela.h" -#include "uscxml/transform/FlatStateIdentifier.h" -#include "uscxml/plugins/datamodel/promela/PromelaParser.h" -#include "uscxml/plugins/datamodel/promela/parser/promela.tab.hpp" - -#include -#include -#include "uscxml/UUID.h" -#include -#include -#include - -#define MAX_MACRO_CHARS 64 -#define MIN_COMMENT_PADDING 60 - -#define BIT_WIDTH(number) (number > 1 ? (int)ceil(log((double)number) / log((double)2.0)) : 1) - -#define INDENT_MIN(stream, start, cols) \ -for (int indentIndex = start; indentIndex < cols; indentIndex++) \ - stream << " "; - -namespace uscxml { - -using namespace Arabica::DOM; -using namespace Arabica::XPath; - -void FSMToPromela::writeProgram(std::ostream& stream, - const Interpreter& interpreter) { - FSMToPromela promelaWriter; - interpreter.getImpl()->copyTo(&promelaWriter); - promelaWriter.writeProgram(stream); -} - -FSMToPromela::FSMToPromela() { -} - -void PromelaEventSource::writeStartEventSources(std::ostream& stream, int indent) { - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - std::list::iterator sourceIter = eventSources.inlines.begin(); - int i = 0; - while(sourceIter != eventSources.inlines.end()) { - if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { - sourceIter++; - continue; - } - std::string sourceName = name + "_"+ toStr(i); - stream << padding << "run " << sourceName << "EventSource();" << std::endl; - - i++; - sourceIter++; - } - -} - -void PromelaEventSource::writeStopEventSources(std::ostream& stream, int indent) { - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - std::list::iterator sourceIter = eventSources.inlines.begin(); - int i = 0; - while(sourceIter != eventSources.inlines.end()) { - if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { - sourceIter++; - continue; - } - std::string sourceName = name + "_"+ toStr(i); - stream << padding << sourceName << "EventSourceDone = 1;" << std::endl; - - i++; - sourceIter++; - } - -} - -void PromelaEventSource::writeDeclarations(std::ostream& stream, int indent) { - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - std::list::iterator sourceIter = eventSources.inlines.begin(); - int i = 0; - while(sourceIter != eventSources.inlines.end()) { - if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { - sourceIter++; - continue; - } - std::string sourceName = name + "_"+ toStr(i); - stream << "bool " << sourceName << "EventSourceDone = 0;" << std::endl; - - i++; - sourceIter++; - } -} - -void PromelaEventSource::writeEventSource(std::ostream& stream) { - - std::list::iterator sourceIter = eventSources.inlines.begin(); - int i = 0; - while(sourceIter != eventSources.inlines.end()) { - if (sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM && sourceIter->type != PromelaInline::PROMELA_EVENT_SOURCE) { - sourceIter++; - continue; - } - - std::string sourceName = name + "_"+ toStr(i); - - stream << "proctype " << sourceName << "EventSource() {" << std::endl; - stream << " " << sourceName << "EventSourceDone = 0;" << std::endl; - stream << " " << sourceName << "NewEvent:" << std::endl; - stream << " " << "if" << std::endl; - stream << " " << ":: " << sourceName << "EventSourceDone -> skip;" << std::endl; - stream << " " << ":: else { " << std::endl; - - Trie& trie = analyzer->getTrie(); - - if (sourceIter->type == PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM) { - std::string content = sourceIter->content; - - boost::replace_all(content, "#REDO#", sourceName + "NewEvent"); - boost::replace_all(content, "#DONE#", sourceName + "Done"); - - std::list eventNames = trie.getChildsWithWords(trie.getNodeWithPrefix("")); - std::list::iterator eventNameIter = eventNames.begin(); - while(eventNameIter != eventNames.end()) { - boost::replace_all(content, "#" + (*eventNameIter)->value + "#", (*eventNameIter)->identifier); - eventNameIter++; - } - - stream << FSMToPromela::beautifyIndentation(content, 2) << std::endl; - - } else { - stream << " " << " if" << std::endl; -// stream << " " << " :: 1 -> " << "goto " << sourceName << "NewEvent;" << std::endl; - - std::list >::const_iterator seqIter = sourceIter->sequences.begin(); - while(seqIter != sourceIter->sequences.end()) { - stream << " " << ":: "; - std::list::const_iterator evIter = seqIter->begin(); - while(evIter != seqIter->end()) { - TrieNode* node = trie.getNodeWithPrefix(*evIter); - stream << "eQ!" << node->identifier << "; "; - evIter++; - } - stream << "goto " << sourceName << "NewEvent;" << std::endl; - seqIter++; - } - - stream << " " << " fi;" << std::endl; - } - - stream << " " << "}" << std::endl; - stream << " " << "fi;" << std::endl; - stream << sourceName << "Done:" << " skip;" << std::endl; - stream << "}" << std::endl; - - i++; - sourceIter++; - } -} - -PromelaEventSource::PromelaEventSource() { - type = PROMELA_EVENT_SOURCE_INVALID; - analyzer = NULL; -} - -PromelaEventSource::PromelaEventSource(const PromelaInlines& sources, const Arabica::DOM::Node& parent) { - type = PROMELA_EVENT_SOURCE_INVALID; - analyzer = NULL; - - eventSources = sources; - container = parent; -} - -void PromelaCodeAnalyzer::addCode(const std::string& code) { - PromelaParser parser(code); - - // find all strings - std::list astNodes; - astNodes.push_back(parser.ast); - - while(astNodes.size() > 0) { - PromelaParserNode* node = astNodes.front(); - astNodes.pop_front(); - - switch (node->type) { - case PML_STRING: { - std::string unquoted = node->value; - if (boost::starts_with(unquoted, "'")) { - unquoted = unquoted.substr(1, unquoted.size() - 2); - } - addLiteral(unquoted); - break; - } - case PML_CMPND: { - std::string nameOfType; - std::list::iterator opIter = node->operands.begin(); - assert((*opIter)->type == PML_NAME); - - PromelaTypedef* td = &_typeDefs; - std::string seperator; - - while(opIter != node->operands.end()) { - switch ((*opIter)->type) { - case PML_NAME: - td = &td->types[(*opIter)->value]; - nameOfType += seperator + (*opIter)->value; - if (nameOfType.compare("_x") == 0) - _usesPlatformVars = true; - seperator = "_"; - td->name = nameOfType + "_t"; - break; - case PML_VAR_ARRAY: { - PromelaParserNode* name = (*opIter)->operands.front(); - PromelaParserNode* subscript = *(++(*opIter)->operands.begin()); - td = &td->types[name->value]; - nameOfType += seperator + name->value; - td->name = nameOfType + "_t"; - - if (isInteger(subscript->value.c_str(), 10)) { - td->arraySize = strTo(subscript->value); - } - break; - } - default: - node->dump(); - assert(false); - break; - } - - if (nameOfType.compare("_x_states") == 0) { - _usesInPredicate = true; - } - if (nameOfType.compare("_event_type") == 0) { - addLiteral("internal"); - addLiteral("external"); - addLiteral("platform"); - } - if (nameOfType.compare("_event_origintype") == 0) { - addLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor"); - } - opIter++; - } - break; - } - case PML_NAME: { - } - default: - break; -// node->dump(); -// assert(false); - } - - astNodes.merge(node->operands); - } -} - -void PromelaCodeAnalyzer::addEvent(const std::string& eventName) { - if (_events.find(eventName) != _events.end()) - return; - - addLiteral(eventName, _lastEventIndex); - assert(_strIndex.find(eventName) != _strIndex.end()); - - _eventTrie.addWord(eventName); - _events[eventName] = _strIndex[eventName]; - _lastEventIndex++; -} - -void PromelaCodeAnalyzer::addState(const std::string& stateName) { - if (_states.find(stateName) != _states.end()) - return; - - createMacroName(stateName); - -// addLiteral(stateName); -// _states[stateName] = enumerateLiteral(stateName); - if (_origStateMap.find(stateName) == _origStateMap.end()) { - FlatStateIdentifier flatId(stateName); - _origStateMap[stateName] = flatId.getActive(); - for (std::list::iterator origIter = _origStateMap[stateName].begin(); origIter != _origStateMap[stateName].end(); origIter++) { - //addLiteral(*origIter); // add original state names as string literals - if (_origStateIndex.find(*origIter) == _origStateIndex.end()) { - _origStateIndex[*origIter] = _lastStateIndex++; - createMacroName(*origIter); - } - } - } -} - -int PromelaCodeAnalyzer::arrayIndexForOrigState(const std::string& stateName) { - if (_origStateIndex.find(stateName) == _origStateIndex.end()) - throw std::runtime_error("No original state " + stateName + " known"); - return _origStateIndex[stateName]; -} - -void PromelaCodeAnalyzer::addLiteral(const std::string& literal, int forceIndex) { - if (boost::starts_with(literal, "'")) - throw std::runtime_error("Literal " + literal + " passed with quotes"); - - if (_strLiterals.find(literal) != _strLiterals.end()) - return; - - _strLiterals.insert(literal); - createMacroName(literal); - enumerateLiteral(literal, forceIndex); -} - -int PromelaCodeAnalyzer::enumerateLiteral(const std::string& literal, int forceIndex) { - if (forceIndex >= 0) { - _strIndex[literal] = forceIndex; - return forceIndex; - } - - if (_strIndex.find(literal) != _strIndex.end()) - return _strIndex[literal]; - - _strIndex[literal] = ++_lastStrIndex; - return _lastStrIndex + 1; -} - -std::string PromelaCodeAnalyzer::createMacroName(const std::string& literal) { - if (_strMacroNames.find(literal) != _strMacroNames.end()) - return _strMacroNames[literal]; - - // find a suitable macro name for the strings - std::string macroName = literal; //literal.substr(1, literal.size() - 2); - - // cannot start with digit - if (isInteger(macroName.substr(0,1).c_str(), 10)) - macroName = "_" + macroName; - - macroName = macroName.substr(0, MAX_MACRO_CHARS); - boost::to_upper(macroName); - - std::string illegalChars = "#\\/:?\"<>| \n\t()[]{}',.-"; - std::string tmp; - std::string::iterator it = macroName.begin(); - while (it < macroName.end()) { - bool found = illegalChars.find(*it) != std::string::npos; - if(found) { - tmp += '_'; - it++; - while(it < macroName.end() && illegalChars.find(*it) != std::string::npos) { - it++; - } - } else { - tmp += *it++; - } - } - macroName = tmp; - - unsigned int index = 2; - while (_macroNameSet.find(macroName) != _macroNameSet.end()) { - std::string suffix = toStr(index); - if (macroName.size() > suffix.size()) { - macroName = macroName.substr(0, macroName.size() - suffix.size()) + suffix; - } else { - macroName = suffix; - } - index++; - } - - _macroNameSet.insert(macroName); - _strMacroNames[literal] = macroName; - return macroName; -} - -std::string PromelaCodeAnalyzer::macroForLiteral(const std::string& literal) { - if (boost::starts_with(literal, "'")) - throw std::runtime_error("Literal " + literal + " passed with quotes"); - - if (_strMacroNames.find(literal) == _strMacroNames.end()) - throw std::runtime_error("No macro for literal " + literal + " known"); - return _strMacroNames[literal]; -} - -int PromelaCodeAnalyzer::indexForLiteral(const std::string& literal) { - if (boost::starts_with(literal, "'")) - throw std::runtime_error("Literal " + literal + " passed with quotes"); - - if (_strIndex.find(literal) == _strIndex.end()) - throw std::runtime_error("No index for literal " + literal + " known"); - return _strIndex[literal]; -} - -std::string PromelaCodeAnalyzer::replaceLiterals(const std::string code) { - std::string replaced = code; - for (std::map::const_iterator litIter = _strMacroNames.begin(); litIter != _strMacroNames.end(); litIter++) { - boost::replace_all(replaced, "'" + litIter->first + "'", litIter->second); - } - return replaced; -} - -std::set PromelaCodeAnalyzer::getEventsWithPrefix(const std::string& prefix) { - std::set eventNames; - std::list trieNodes = _eventTrie.getWordsWithPrefix(prefix); - - std::list::iterator trieIter = trieNodes.begin(); - while(trieIter != trieNodes.end()) { - eventNames.insert((*trieIter)->value); - trieIter++; - } - - return eventNames; -} - - -void FSMToPromela::writeEvents(std::ostream& stream) { - std::map events = _analyzer.getEvents(); - std::map::iterator eventIter = events.begin(); - stream << "/* event name identifiers */" << std::endl; - while(eventIter != events.end()) { - if (eventIter->first.length() > 0) { - stream << "#define " << _analyzer.macroForLiteral(eventIter->first) << " " << _analyzer.indexForLiteral(eventIter->first); - stream << " /* from \"" << eventIter->first << "\" */" << std::endl; - } - eventIter++; - } -} - -void FSMToPromela::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_CAST(_globalStates[i], "id") << "\" */" << std::endl; - } -} - -void FSMToPromela::writeStateMap(std::ostream& stream) { - stream << "/* macros for original state names */" << std::endl; - std::map origStates = _analyzer.getOrigStates(); - for (std::map::iterator origIter = origStates.begin(); origIter != origStates.end(); origIter++) { - stream << "#define " << _analyzer.macroForLiteral(origIter->first) << " " << origIter->second; - stream << " /* from \"" << origIter->first << "\" */" << std::endl; - } - -// std::map states = _analyzer.getStates(); -// size_t stateIndex = 0; -// for (std::map::iterator stateIter = states.begin(); stateIter != states.end(); stateIter++) { -// stream << "_x" -// std::list origStates = _analyzer.getOrigState(stateIter->first); -// size_t origIndex = 0; -// for (std::list::iterator origIter = origStates.begin(); origIter != origStates.end(); origIter++) { -// -// } -// } -} - -void FSMToPromela::writeTypeDefs(std::ostream& stream) { - stream << "/* typedefs */" << std::endl; - PromelaCodeAnalyzer::PromelaTypedef typeDefs = _analyzer.getTypes(); - if (typeDefs.types.size() == 0) - return; - - std::list individualDefs; - std::list currDefs; - currDefs.push_back(typeDefs); - - while(currDefs.size() > 0) { - if (std::find(individualDefs.begin(), individualDefs.end(), currDefs.front()) == individualDefs.end()) { - individualDefs.push_back(currDefs.front()); - for (std::map::iterator typeIter = currDefs.front().types.begin(); typeIter != currDefs.front().types.end(); typeIter++) { - currDefs.push_back(typeIter->second); - } - } - currDefs.pop_front(); - } - individualDefs.pop_front(); - - for (std::list::reverse_iterator rIter = individualDefs.rbegin(); rIter != individualDefs.rend(); rIter++) { - PromelaCodeAnalyzer::PromelaTypedef currDef = *rIter; - if (currDef.types.size() == 0 || currDef.name.size() == 0) - continue; -// if (currDef.name.compare("_x_t") == 0) { -// stream << "typedef platform_t {" << std::endl; -// if (_analyzer.usesInPredicate()) { -// stream << " bool states[" << _analyzer.getOrigStates().size() << "];" << std::endl; -// } -// stream << "};" << std::endl; -// -// continue; -// } - stream << "typedef " << currDef.name << " {" << std::endl; - if (currDef.name.compare("_event_t") == 0 && currDef.types.find("name") == currDef.types.end()) { // special treatment for _event - stream << " int name;" << std::endl; - } - for (std::map::iterator tIter = currDef.types.begin(); tIter != currDef.types.end(); tIter++) { - if (currDef.name.compare("_x_t") == 0 && tIter->first.compare("states") == 0) { - stream << " bool states[" << _analyzer.getOrigStates().size() << "];" << std::endl; - continue; - } - if (tIter->second.types.size() == 0) { - stream << " int " << tIter->first << ";" << std::endl; // not further nested - } else { - stream << " " << tIter->second.name << " " << tIter->first << ";" << std::endl; - } - } - stream << "};" << std::endl << std::endl; - } - - stream << "/* typedef instances */" << std::endl; - PromelaCodeAnalyzer::PromelaTypedef allTypes = _analyzer.getTypes(); - std::map::iterator typeIter = allTypes.types.begin(); - while(typeIter != allTypes.types.end()) { - stream << typeIter->second.name << " " << typeIter->first << ";" << std::endl; - typeIter++; - } - -} - -Arabica::XPath::NodeSet FSMToPromela::getTransientContent(const Arabica::DOM::Element& state, const std::string& source) { - Arabica::XPath::NodeSet content; - Arabica::DOM::Element currState = state; - FlatStateIdentifier prevFlatId(source); - for (;;) { - if (_analyzer.usesInPredicate()) { - // insert state assignments into executable content - - std::stringstream stateSetPromela; - stateSetPromela << "#promela-inline " << std::endl; - FlatStateIdentifier currFlatId(ATTR(currState, "id")); - stateSetPromela << " /* from " << prevFlatId.getFlatActive() << " to " << currFlatId.getFlatActive() << " */" << std::endl; - - // add all that are missing from prevFlatId - std::map allOrigStates = _analyzer.getOrigStates(); - for (std::map::iterator allOrigIter = allOrigStates.begin(); allOrigIter != allOrigStates.end(); allOrigIter++) { - if (std::find(currFlatId.getActive().begin(), currFlatId.getActive().end(), allOrigIter->first) != currFlatId.getActive().end() && - std::find(prevFlatId.getActive().begin(), prevFlatId.getActive().end(), allOrigIter->first) == prevFlatId.getActive().end()) { - // active now but not previously - stateSetPromela << " _x.states[" << _analyzer.macroForLiteral(allOrigIter->first) << "] = true; " << std::endl; - } else if (std::find(currFlatId.getActive().begin(), currFlatId.getActive().end(), allOrigIter->first) == currFlatId.getActive().end() && - std::find(prevFlatId.getActive().begin(), prevFlatId.getActive().end(), allOrigIter->first) != prevFlatId.getActive().end()) { - // previously active but not now - stateSetPromela << " _x.states[" << _analyzer.macroForLiteral(allOrigIter->first) << "] = false; " << std::endl; - } - } - Comment comment = _document.createComment(stateSetPromela.str()); - _document.importNode(comment, true); - currState.insertBefore(comment, currState.getFirstChild()); - prevFlatId = currFlatId; - } - - content.push_back(filterChildType(Node_base::COMMENT_NODE, currState)); - if (_analyzer.usesInPredicate()) assert(content.size() > 0); - - if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) { - // breaking here causes final state assignment to be written - 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 transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); - currState = _states[ATTR_CAST(transitions[0], "target")]; - } - return content; -} - -Node FSMToPromela::getUltimateTarget(const Arabica::DOM::Element& transition) { - if (!HAS_ATTR(transition, "target")) { - return transition.getParentNode(); - } - - Arabica::DOM::Element currState = _states[ATTR_CAST(transition, "target")]; - - for (;;) { - if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) - return currState; - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); - currState = _states[ATTR_CAST(transitions[0], "target")]; - } -} - -void FSMToPromela::writeInlineComment(std::ostream& stream, const Arabica::DOM::Node& 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 FSMToPromela::writeExecutableContent(std::ostream& stream, const Arabica::DOM::Node& node, int indent) { - -// std::cout << std::endl << node << std::endl; - if (!node) - return; - - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - -// std::cerr << node << std::endl; - - if (node.getNodeType() == Node_base::COMMENT_NODE) { - // we cannot have labels in an atomic block, just process inline promela - 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; - - Arabica::DOM::Element nodeElem = Arabica::DOM::Element(node); - - if (false) { -// } else if(TAGNAME(nodeElem) == "state") { -// if (HAS_ATTR(nodeElem, "transient") && DOMUtils::attributeIsTrue(ATTR(nodeElem, "transient"))) { -// Arabica::XPath::NodeSet execContent = getTransientContent(nodeElem); -// for (int i = 0; i < execContent.size(); i++) { -// writeExecutableContent(stream, execContent[i], indent); -// } -// } - } else if(TAGNAME(nodeElem) == "transition") { - stream << "t" << _transitions[nodeElem] << ":"; - - int number = _transitions[nodeElem]; - int digits = 0; - do { - number /= 10; - digits++; - } while (number != 0); - - INDENT_MIN(stream, 2 + digits, MIN_COMMENT_PADDING); - - Node source = node.getParentNode(); - stream << " /* from state " << ATTR_CAST(source, "id") << " */" << std::endl; - - // gather all executable content - NodeSet execContent = getTransientContent(_states[ATTR(nodeElem, "target")], ATTR_CAST(source, "id")); - - // check for special promela labels - if (HAS_ATTR(nodeElem, "target")) { - PromelaInlines promInls = getInlinePromela(execContent, true); - - if (promInls.acceptLabels > 0) - stream << padding << "acceptLabelT" << _transitions[nodeElem] << ":" << std::endl; - if (promInls.endLabels > 0) - stream << padding << "endLabelT" << _transitions[nodeElem] << ":" << std::endl; - if (promInls.progressLabels > 0) - stream << padding << "progressLabelT" << _transitions[nodeElem] << ":" << std::endl; - } - - stream << padding << "atomic {" << std::endl; -// writeExecutableContent(stream, _states[ATTR(nodeElem, "target")], indent+1); - for (int i = 0; i < execContent.size(); i++) { - writeExecutableContent(stream, execContent[i], indent+1); - } - stream << padding << " skip;" << std::endl; - - Node newState = getUltimateTarget(nodeElem); - for (int i = 0; i < _globalStates.size(); i++) { - if (newState != _globalStates[i]) - continue; - - std::string stateId = ATTR_CAST(_globalStates[i], "id"); - - stream << padding << " s = s" << i << ";"; - - int number = i; - int digits = 0; - do { - number /= 10; - digits++; - } while (number != 0); - - INDENT_MIN(stream, 10 + digits, MIN_COMMENT_PADDING); - - stream << " /* to state " << stateId << " */" << std::endl; - -// if (_analyzer.usesInPredicate()) { -// FlatStateIdentifier flatId(stateId); -// std::map allOrigStates = _analyzer.getOrigStates(); -// for (std::map::iterator allOrigIter = allOrigStates.begin(); allOrigIter != allOrigStates.end(); allOrigIter++) { -// stream << padding << " _x.states[" << _analyzer.macroForLiteral(allOrigIter->first) << "] = "; -// if (std::find(flatId.getActive().begin(), flatId.getActive().end(), allOrigIter->first) != flatId.getActive().end()) { -// stream << "true;" << std::endl; -// } else { -// stream << "false;" << std::endl; -// } -// } -// } - - } - - stream << padding << "}" << std::endl; - if (isFinal(Element(newState))) { - stream << padding << "goto terminate;"; - INDENT_MIN(stream, padding.length() + 14, MIN_COMMENT_PADDING); - stream << "/* final state */" << std::endl; - } else if (!HAS_ATTR_CAST(node, "event")) { - stream << padding << "goto nextTransition;"; - INDENT_MIN(stream, padding.length() + 19, MIN_COMMENT_PADDING); - stream << "/* spontaneous transition, check for more transitions */" << std::endl; - } else { - stream << padding << "eventLess = true;" << std::endl; - stream << padding << "goto nextTransition;"; - INDENT_MIN(stream, padding.length() + 21, MIN_COMMENT_PADDING); - stream << "/* ordinary transition, check for spontaneous transitions */" << std::endl; - } - - } else if(TAGNAME(nodeElem) == "onentry" || TAGNAME(nodeElem) == "onexit") { - Arabica::DOM::Node child = node.getFirstChild(); - while(child) { -// std::cerr << node << std::endl; - if (child.getNodeType() == Node_base::TEXT_NODE) { - if (boost::trim_copy(child.getNodeValue()).length() > 0) - stream << beautifyIndentation(_analyzer.replaceLiterals(child.getNodeValue()), indent) << std::endl; - } - if (child.getNodeType() == Node_base::ELEMENT_NODE) { - writeExecutableContent(stream, child, indent); - } - child = child.getNextSibling(); - } - - } else if(TAGNAME(nodeElem) == "script") { - NodeSet scriptText = filterChildType(Node_base::TEXT_NODE, node, true); - for (int i = 0; i < scriptText.size(); i++) { - stream << _analyzer.replaceLiterals(beautifyIndentation(scriptText[i].getNodeValue(), indent)) << std::endl; - } - - } else if(TAGNAME(nodeElem) == "log") { - std::string label = (HAS_ATTR(nodeElem, "label") ? ATTR(nodeElem, "label") : ""); - std::string expr = (HAS_ATTR(nodeElem, "expr") ? ATTR(nodeElem, "expr") : ""); - std::string trimmedExpr = boost::trim_copy(expr); - bool isStringLiteral = (boost::starts_with(trimmedExpr, "\"") || boost::starts_with(trimmedExpr, "'")); - - std::string formatString; - std::string varString; - std::string seperator; - - if (label.size() > 0) { - formatString += label + ": "; - } - - if (isStringLiteral) { - formatString += expr; - } else { - formatString += "%d"; - varString += seperator + expr; - } - - if (varString.length() > 0) { - stream << padding << "printf(\"" + formatString + "\", " + varString + ");" << std::endl; - } else { - stream << padding << "printf(\"" + formatString + "\");" << std::endl; - } - - } else if(TAGNAME(nodeElem) == "foreach") { - stream << padding << "for (" << (HAS_ATTR(nodeElem, "index") ? ATTR(nodeElem, "index") : "_index") << " in " << ATTR(nodeElem, "array") << ") {" << std::endl; - if (HAS_ATTR(nodeElem, "item")) { - stream << padding << " " << ATTR(nodeElem, "item") << " = " << ATTR(nodeElem, "array") << "[" << (HAS_ATTR(nodeElem, "index") ? ATTR(nodeElem, "index") : "_index") << "];" << std::endl; - } - Arabica::DOM::Node child = node.getFirstChild(); - while(child) { - writeExecutableContent(stream, child, indent + 1); - child = child.getNextSibling(); - } - if (HAS_ATTR(nodeElem, "index")) - stream << padding << " " << ATTR(nodeElem, "index") << "++;" << std::endl; - stream << padding << "}" << std::endl; - - } else if(TAGNAME(nodeElem) == "if") { - NodeSet 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(nodeElem) == "assign") { - if (HAS_ATTR(nodeElem, "location")) { - stream << padding << ATTR(nodeElem, "location") << " = "; - } - if (HAS_ATTR(nodeElem, "expr")) { - stream << _analyzer.replaceLiterals(ATTR(nodeElem, "expr")) << ";" << std::endl; - } else { - NodeSet assignTexts = filterChildType(Node_base::TEXT_NODE, nodeElem, true); - if (assignTexts.size() > 0) { - stream << _analyzer.replaceLiterals(boost::trim_copy(assignTexts[0].getNodeValue())) << ";" << std::endl; - } - } - } else if(TAGNAME(nodeElem) == "send" || TAGNAME(nodeElem) == "raise") { - std::string targetQueue; - if (TAGNAME(nodeElem) == "raise") { - targetQueue = "iQ!"; - } else if (!HAS_ATTR(nodeElem, "target")) { - targetQueue = "tmpQ!"; - } else if (ATTR(nodeElem, "target").compare("#_internal") == 0) { - targetQueue = "iQ!"; - } - if (targetQueue.length() > 0) { - // this is for our external queue - std::string event; - - if (HAS_ATTR(nodeElem, "event")) { - event = _analyzer.macroForLiteral(ATTR(nodeElem, "event")); - } else if (HAS_ATTR(nodeElem, "eventexpr")) { - event = ATTR(nodeElem, "eventexpr"); - } - if (_analyzer.usesComplexEventStruct()) { - stream << padding << "{" << std::endl; - stream << padding << " _event_t tmpEvent;" << std::endl; - stream << padding << " tmpEvent.name = " << event << ";" << std::endl; - - if (HAS_ATTR(nodeElem, "idlocation")) { - stream << padding << " /* idlocation */" << std::endl; - stream << padding << " _lastSendId = _lastSendId + 1;" << std::endl; - stream << padding << " " << ATTR(nodeElem, "idlocation") << " = _lastSendId;" << std::endl; - stream << padding << " tmpEvent.sendid = _lastSendId;" << std::endl; - stream << padding << " if" << std::endl; - stream << padding << " :: _lastSendId == 2147483647 -> _lastSendId = 0;" << std::endl; - stream << padding << " :: timeout -> skip;" << std::endl; - stream << padding << " fi;" << std::endl; - } else if (HAS_ATTR(nodeElem, "id")) { - stream << padding << " tmpEvent.sendid = " << _analyzer.macroForLiteral(ATTR(nodeElem, "id")) << ";" << std::endl; - } - - if (_analyzer.usesEventField("origintype") && targetQueue.compare("iQ!") != 0) { - stream << padding << " tmpEvent.origintype = " << _analyzer.macroForLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor") << ";" << std::endl; - } - - if (_analyzer.usesEventField("type")) { - std::string eventType = (targetQueue.compare("iQ!") == 0 ? _analyzer.macroForLiteral("internal") : _analyzer.macroForLiteral("external")); - stream << padding << " tmpEvent.type = " << eventType << ";" << std::endl; - } - - NodeSet sendParams = filterChildElements(_nsInfo.xmlNSPrefix + "param", nodeElem); - NodeSet sendContents = filterChildElements(_nsInfo.xmlNSPrefix + "content", nodeElem); - std::string sendNameList = ATTR(nodeElem, "namelist"); - if (sendParams.size() > 0) { - for (int i = 0; i < sendParams.size(); i++) { - Element paramElem = Element(sendParams[i]); - stream << padding << " tmpEvent.data." << ATTR(paramElem, "name") << " = " << ATTR(paramElem, "expr") << ";" << std::endl; - } - } - if (sendNameList.size() > 0) { - std::list nameListIds = tokenizeIdRefs(sendNameList); - std::list::iterator nameIter = nameListIds.begin(); - while(nameIter != nameListIds.end()) { - stream << padding << " tmpEvent.data." << *nameIter << " = " << *nameIter << ";" << std::endl; - nameIter++; - } - } - - if (sendParams.size() == 0 && sendNameList.size() == 0 && sendContents.size() > 0) { - Element contentElem = Element(sendContents[0]); - if (contentElem.hasChildNodes() && contentElem.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { - stream << padding << " tmpEvent.data = " << spaceNormalize(contentElem.getFirstChild().getNodeValue()) << ";" << std::endl; - } else if (HAS_ATTR(contentElem, "expr")) { - stream << padding << " tmpEvent.data = " << _analyzer.replaceLiterals(ATTR(contentElem, "expr")) << ";" << std::endl; - } - } - stream << padding << " " << targetQueue << "tmpEvent;" << std::endl; - stream << padding << "}" << std::endl; - } else { - stream << padding << targetQueue << event << ";" << std::endl; - } - } - } else if(TAGNAME(nodeElem) == "invoke") { - _invokers[ATTR(nodeElem, "invokeid")].writeStartEventSources(stream, indent); - } else if(TAGNAME(nodeElem) == "uninvoke") { - stream << padding << ATTR(nodeElem, "invokeid") << "EventSourceDone" << "= 1;" << std::endl; - } else { - - std::cerr << "'" << TAGNAME(nodeElem) << "'" << std::endl << nodeElem << std::endl; - assert(false); - } - -} - -PromelaInlines FSMToPromela::getInlinePromela(const std::string& content) { - PromelaInlines prom; - - std::stringstream ssLine(content); - std::string line; - - bool isInPromelaCode = false; - bool isInPromelaEventSource = false; - PromelaInline promInl; - - while(std::getline(ssLine, line)) { - std::string trimLine = boost::trim_copy(line); - if (trimLine.length() == 0) - continue; - if (boost::starts_with(trimLine, "#promela")) { - if (isInPromelaCode || isInPromelaEventSource) { - prom.inlines.push_back(promInl); - isInPromelaCode = false; - isInPromelaEventSource = false; - } - promInl = PromelaInline(); - } - - if (false) { - } else if (boost::starts_with(trimLine, "#promela-progress")) { - prom.progressLabels++; - promInl.type = PromelaInline::PROMELA_PROGRESS_LABEL; - promInl.content = line; - prom.inlines.push_back(promInl); - } else if (boost::starts_with(trimLine, "#promela-accept")) { - prom.acceptLabels++; - promInl.type = PromelaInline::PROMELA_ACCEPT_LABEL; - promInl.content = line; - prom.inlines.push_back(promInl); - } else if (boost::starts_with(trimLine, "#promela-end")) { - prom.endLabels++; - promInl.type = PromelaInline::PROMELA_END_LABEL; - promInl.content = line; - prom.inlines.push_back(promInl); - } else if (boost::starts_with(trimLine, "#promela-inline")) { - prom.codes++; - isInPromelaCode = true; - promInl.type = PromelaInline::PROMELA_CODE; - } else if (boost::starts_with(trimLine, "#promela-event-source-custom")) { - prom.customEventSources++; - isInPromelaCode = true; - promInl.type = PromelaInline::PROMELA_EVENT_SOURCE_CUSTOM; - } else if (boost::starts_with(trimLine, "#promela-event-source")) { - prom.eventSources++; - isInPromelaEventSource = true; - promInl.type = PromelaInline::PROMELA_EVENT_SOURCE; - } else if (isInPromelaCode) { - promInl.content += line; - promInl.content += "\n"; - } else if (isInPromelaEventSource) { - std::list seq; - std::stringstream ssToken(trimLine); - std::string token; - while(std::getline(ssToken, token, ' ')) { - if (token.length() == 0) - continue; - seq.push_back(token); - } - promInl.sequences.push_back(seq); - } - } - // inline code ends with comment - if (isInPromelaCode || isInPromelaEventSource) { - prom.inlines.push_back(promInl); - } - - return prom; -} - -PromelaInlines FSMToPromela::getInlinePromela(const Arabica::DOM::Node& node) { - if (node.getNodeType() != Node_base::COMMENT_NODE) - return getInlinePromela(std::string()); - return getInlinePromela(node.getNodeValue()); -} - -PromelaInlines FSMToPromela::getInlinePromela(const Arabica::XPath::NodeSet& elements, bool recurse) { - PromelaInlines allPromInls; - - Arabica::XPath::NodeSet comments = filterChildType(Node_base::COMMENT_NODE, elements, recurse); - for (int i = 0; i < comments.size(); i++) { - allPromInls.merge(getInlinePromela(comments[i])); - } - return allPromInls; -} - -void FSMToPromela::writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet& 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_CAST(condChain[1]) == "else") { - nextIsElse = true; - } - } - - Element ifNode = Element(condChain[0]); - - stream << padding << "if" << std::endl; - // we need to nest the elseifs to resolve promela if semantics - stream << padding << ":: (" << _analyzer.replaceLiterals(ATTR(ifNode, "cond")) << ") -> {" << std::endl; - - Arabica::DOM::Node child; - if (TAGNAME(ifNode) == "if") { - child = ifNode.getFirstChild(); - } else { - child = ifNode.getNextSibling(); - } - while(child) { - if (child.getNodeType() == Node_base::ELEMENT_NODE) { - Arabica::DOM::Element childElem = Arabica::DOM::Element(child); - if (TAGNAME(childElem) == "elseif" || TAGNAME_CAST(childElem) == "else") - break; - writeExecutableContent(stream, childElem, indent + 1); - } - child = child.getNextSibling(); - } - stream << padding << "}" << std::endl; - stream << padding << ":: else -> "; - - if (nextIsElse) { - child = condChain[1].getNextSibling(); - stream << "{" << std::endl; - while(child) { - if (child.getNodeType() == Node_base::ELEMENT_NODE) { - 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 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 FSMToPromela::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 FSMToPromela::writeStrings(std::ostream& stream) { - stream << "/* string literals */" << std::endl; - std::set literals = _analyzer.getLiterals(); - std::map events = _analyzer.getEvents(); - std::map origStates = _analyzer.getOrigStates(); - - for (std::set::const_iterator litIter = literals.begin(); litIter != literals.end(); litIter++) { - if (events.find(*litIter) == events.end() && (origStates.find(*litIter) == origStates.end() || !_analyzer.usesInPredicate())) - stream << "#define " << _analyzer.macroForLiteral(*litIter) << " " << _analyzer.indexForLiteral(*litIter) << " /* " << *litIter << " */" << std::endl; - } -} - -void FSMToPromela::writeDeclarations(std::ostream& stream) { - - // get all data elements - NodeSet datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); -// NodeSet dataText = filterChildType(Node_base::TEXT_NODE, datas, true); - - // write their text content - stream << "/* datamodel variables */" << std::endl; - std::set processedIdentifiers; - for (int i = 0; i < datas.size(); i++) { - - Node data = datas[i]; - if (isInEmbeddedDocument(data)) - continue; - - std::string identifier = (HAS_ATTR_CAST(data, "id") ? ATTR_CAST(data, "id") : ""); - std::string expression = (HAS_ATTR_CAST(data, "expr") ? ATTR_CAST(data, "expr") : ""); - std::string type = boost::trim_copy(HAS_ATTR_CAST(data, "type") ? ATTR_CAST(data, "type") : ""); - - if (processedIdentifiers.find(identifier) != processedIdentifiers.end()) - continue; - processedIdentifiers.insert(identifier); - - if (boost::starts_with(type, "string")) { - type = "int" + type.substr(6, type.length() - 6); - } - std::string arrSize; - - NodeSet dataText = filterChildType(Node_base::TEXT_NODE, data, true); - std::string value; - if (dataText.size() > 0) { - value = dataText[0].getNodeValue(); - boost::trim(value); - } - - if (identifier.length() > 0) { - - size_t bracketPos = type.find("["); - if (bracketPos != std::string::npos) { - arrSize = type.substr(bracketPos, type.length() - bracketPos); - type = type.substr(0, bracketPos); - } - std::string decl = type + " " + identifier + arrSize; - - if (arrSize.length() > 0) { - stream << decl << ";" << std::endl; - _varInitializers.push_back(value); - } else { - stream << decl; - if (expression.length() > 0) { - // id and expr given - stream << " = " << _analyzer.replaceLiterals(boost::trim_copy(expression)) << ";" << std::endl; - } else if (value.length() > 0) { - // id and text content given - stream << " = " << _analyzer.replaceLiterals(value) << ";" << std::endl; - } else { - // only id given - stream << ";" << std::endl; - } - } - } else if (value.length() > 0) { - // no id but text content given - stream << beautifyIndentation(value) << std::endl; - } - } - - stream << std::endl; - stream << "/* global variables */" << std::endl; - - if (_analyzer.usesComplexEventStruct()) { - // event is defined with the typedefs - stream << "unsigned s : " << BIT_WIDTH(_globalStates.size() + 1) << "; /* current state */" << std::endl; - stream << "chan iQ = [10] of {_event_t} /* internal queue */" << std::endl; - stream << "chan eQ = [10] of {_event_t} /* external queue */" << std::endl; - stream << "chan tmpQ = [10] of {_event_t} /* temporary queue for external events in transitions */" << std::endl; - stream << "_event_t tmpQItem;" << std::endl; - } else { - stream << "unsigned _event : " << BIT_WIDTH(_analyzer.getEvents().size() + 1) << "; /* current event */" << std::endl; - stream << "unsigned s : " << BIT_WIDTH(_globalStates.size() + 1) << "; /* current state */" << std::endl; - stream << "chan iQ = [10] of {int} /* internal queue */" << std::endl; - stream << "chan eQ = [10] of {int} /* external queue */" << std::endl; - stream << "chan tmpQ = [10] of {int} /* temporary queue for external events in transitions */" << std::endl; - stream << "unsigned tmpQItem : " << BIT_WIDTH(_analyzer.getEvents().size() + 1) << ";" << std::endl; - } - stream << "bool eventLess = true; /* whether to process event-less only n this step */" << std::endl; - stream << "hidden int _index; /* helper for indexless foreach loops */" << std::endl; - - if (_analyzer.usesEventField("sendid")) { - stream << "hidden int _lastSendId = 0; /* sequential counter for send ids */"; - } - -// if (_analyzer.usesPlatformVars()) { -// stream << "_x_t _x;" << std::endl; -// } - - stream << std::endl; - stream << "/* event sources */" << std::endl; - - if (_globalEventSource) { - _globalEventSource.writeDeclarations(stream); - } - - std::map::iterator invIter = _invokers.begin(); - while(invIter != _invokers.end()) { - invIter->second.writeDeclarations(stream); - invIter++; - } - -} - -void FSMToPromela::writeEventSources(std::ostream& stream) { - std::list::iterator inlineIter; - - if (_globalEventSource) { - _globalEventSource.writeEventSource(stream); - } - - std::map::iterator invIter = _invokers.begin(); - while(invIter != _invokers.end()) { - invIter->second.writeEventSource(stream); - invIter++; - } -} - -void FSMToPromela::writeFSM(std::ostream& stream) { - NodeSet 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 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?_event; eQ!_event }" << 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 ? _event /* from internal queue */" << std::endl; - stream << " :: else -> eQ ? _event /* from external queue */" << std::endl; - stream << " fi;" << std::endl << std::endl; - stream << " /* event dispatching per state */" << std::endl; - stream << "nextTransition:" << std::endl; - stream << " if" << std::endl; - - writeEventDispatching(stream); - - stream << " :: else -> assert(false); /* this is an error as we dispatched all valid states */" << std::endl; - stream << " fi;" << std::endl; - stream << "terminate: skip;" << std::endl; - - // stop all event sources - if (_globalEventSource) - _globalEventSource.writeStopEventSources(stream, 1); - - std::map::iterator invIter = _invokers.begin(); - while(invIter != _invokers.end()) { - invIter->second.writeStopEventSources(stream, 1); - invIter++; - } - - stream << "}" << std::endl; -} - -void FSMToPromela::writeEventDispatching(std::ostream& stream) { - for (int i = 0; i < _globalStates.size(); i++) { - if (_globalStates[i] == _startState) - continue; - - int number = i; - int digits = 0; - do { - number /= 10; - digits++; - } while (number != 0); - stream << " :: (s == s" << i << ") -> {"; - - INDENT_MIN(stream, 18 + digits, MIN_COMMENT_PADDING); - - stream << " /* from state " << ATTR_CAST(_globalStates[i], "id") << " */" << std::endl; - - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); - writeDispatchingBlock(stream, transitions, 2); -// stream << " goto nextStep;"; - stream << " }" << std::endl; - } -} - -void FSMToPromela::writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent) { - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - if (transChain.size() == 0) { - stream << padding << "eventLess = false;" << std::endl; - stream << padding << "goto nextStep;"; - INDENT_MIN(stream, padding.size() + 13, MIN_COMMENT_PADDING); - stream << "/* no transition applicable */" << std::endl; - return; - } - - - Element currTrans = Element(transChain[0]); - std::stringstream tmpSS; - - tmpSS << padding << "if" << std::endl; - size_t lineStart = tmpSS.tellp(); - - if (HAS_ATTR(currTrans, "cond")) { - tmpSS << padding << ":: (("; - } else { - tmpSS << padding << ":: ("; - } - - if (!HAS_ATTR(currTrans, "event")) { - tmpSS << "eventLess"; - } else { - std::string eventDescs = ATTR(currTrans, "event"); - - std::list eventNames = tokenizeIdRefs(eventDescs); - std::set eventPrefixes; - std::list::iterator eventNameIter = eventNames.begin(); - while(eventNameIter != eventNames.end()) { - std::string eventDesc = *eventNameIter; - 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.length() > 0) { - std::set tmp = _analyzer.getEventsWithPrefix(*eventNameIter); - eventPrefixes.insert(tmp.begin(), tmp.end()); - } - eventNameIter++; - } - - if (eventPrefixes.size() > 0) { - tmpSS << "!eventLess && "; - } else { - tmpSS << "!eventLess"; - } - - std::string seperator; - std::set::iterator eventIter = eventPrefixes.begin(); - while(eventIter != eventPrefixes.end()) { - if (_analyzer.usesComplexEventStruct()) { - tmpSS << seperator << "_event.name == " << _analyzer.macroForLiteral(*eventIter); - } else { - tmpSS << seperator << "_event == " << _analyzer.macroForLiteral(*eventIter); - } - seperator = " || "; - eventIter++; - } - } - - tmpSS << ")"; - if (HAS_ATTR(currTrans, "cond")) { - tmpSS << (HAS_ATTR(currTrans, "cond") ? " && " + _analyzer.replaceLiterals(ATTR(currTrans, "cond")) + ")": ""); - } - tmpSS << " -> goto t" << _transitions[currTrans] << ";"; - size_t lineEnd = tmpSS.tellp(); - size_t lineLength = lineEnd - lineStart; - - for (int i = lineLength; i < MIN_COMMENT_PADDING; i++) - tmpSS << " "; - - tmpSS << " /* transition to " << ATTR_CAST(getUltimateTarget(currTrans), "id") << " */" << std::endl; - stream << tmpSS.str(); - - stream << padding << ":: else {" << std::endl; - - Arabica::XPath::NodeSet cdrTransChain; - for (int i = 1; i < transChain.size(); i++) { - cdrTransChain.push_back(transChain[i]); - } - writeDispatchingBlock(stream, cdrTransChain, indent + 1); - - stream << padding << "}" << std::endl; - stream << padding << "fi;" << std::endl; -} - - -void FSMToPromela::writeMain(std::ostream& stream) { - stream << std::endl; - stream << "init {" << std::endl; - if (_varInitializers.size() > 0) { - std::list::iterator initIter = _varInitializers.begin(); - while(initIter != _varInitializers.end()) { - stream << beautifyIndentation(*initIter); - initIter++; - } - stream << std::endl; - } - if (_globalEventSource) - _globalEventSource.writeStartEventSources(stream, 1); - stream << " run step();" << std::endl; - stream << "}" << std::endl; - -} - - -void FSMToPromela::initNodes() { - // get all states - NodeSet states = filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml); - for (int i = 0; i < states.size(); i++) { - if (InterpreterImpl::isInEmbeddedDocument(states[i])) - continue; - _states[ATTR_CAST(states[i], "id")] = Element(states[i]); - _analyzer.addState(ATTR_CAST(states[i], "id")); - if (HAS_ATTR_CAST(states[i], "transient") && DOMUtils::attributeIsTrue(ATTR_CAST(states[i], "transient"))) - continue; - _globalStates.push_back(states[i]); - } - _startState = _states[ATTR(_scxml, "initial")]; - - // initialize event trie with all events that might occur - NodeSet 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_CAST(internalEventNames[i], "event")) { - std::string eventNames = ATTR_CAST(internalEventNames[i], "event"); - std::list events = tokenizeIdRefs(eventNames); - for (std::list::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); - if (eventName.size() > 0) - _analyzer.addEvent(eventName); - } - } - } - - // do we need sendid / invokeid? - { - NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true); - NodeSet sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", _scxml, true); - - for (int i = 0; i < invokes.size(); i++) { - if (HAS_ATTR_CAST(invokes[i], "idlocation")) { - } - } - - for (int i = 0; i < sends.size(); i++) { - if (HAS_ATTR_CAST(sends[i], "idlocation")) { - _analyzer.addCode("_event.sendid"); - } - if (HAS_ATTR_CAST(sends[i], "id")) { - _analyzer.addLiteral(ATTR_CAST(sends[i], "id")); - _analyzer.addCode("_event.sendid"); - } - } - - } - // external event names from comments - NodeSet promelaEventSourceComments; - NodeSet invokers = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "invoke", _scxml).asNodeSet(); - promelaEventSourceComments.push_back(filterChildType(Node_base::COMMENT_NODE, invokers, false)); // comments in invoke elements - promelaEventSourceComments.push_back(filterChildType(Node_base::COMMENT_NODE, _scxml, false)); // comments in scxml element - - for (int i = 0; i < promelaEventSourceComments.size(); i++) { - PromelaInlines promInls = getInlinePromela(promelaEventSourceComments[i]); - PromelaEventSource promES(promInls, promelaEventSourceComments[i].getParentNode()); - - if (TAGNAME_CAST(promelaEventSourceComments[i].getParentNode()) == "scxml") { - promES.type = PromelaEventSource::PROMELA_EVENT_SOURCE_GLOBAL; - promES.analyzer = &_analyzer; - promES.name = "global"; - _globalEventSource = promES; - } else if (TAGNAME_CAST(promelaEventSourceComments[i].getParentNode()) == "invoke") { - if (!HAS_ATTR_CAST(promelaEventSourceComments[i].getParentNode(), "invokeid")) { - Element invoker = Element(promelaEventSourceComments[i].getParentNode()); - invoker.setAttribute("invokeid", "invoker" + toStr(_invokers.size())); - } - std::string invokeId = ATTR_CAST(promelaEventSourceComments[i].getParentNode(), "invokeid"); - promES.type = PromelaEventSource::PROMELA_EVENT_SOURCE_INVOKER; - promES.analyzer = &_analyzer; - promES.name = invokeId; - _invokers[invokeId] = promES; - } - } - - // enumerate transitions - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); - int index = 0; - for (int i = 0; i < transitions.size(); i++) { - _transitions[Element(transitions[i])] = index++; - } - - // add platform variables as string literals - _analyzer.addLiteral("_sessionid"); - _analyzer.addLiteral("_name"); - - if (HAS_ATTR(_scxml, "name")) { - _analyzer.addLiteral(ATTR(_scxml, "name"), _analyzer.indexForLiteral("_sessionid")); - } - - NodeSet contents = filterChildElements(_nsInfo.xmlNSPrefix + "content", _scxml, true); - for (int i = 0; i < contents.size(); i++) { - Element contentElem = Element(contents[i]); - if (contentElem.hasChildNodes() && contentElem.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { - _analyzer.addLiteral(spaceNormalize(contentElem.getFirstChild().getNodeValue())); - } - } - - - // extract and analyze source code - std::set allCode; - std::set allStrings; - { - NodeSet withCond; - withCond.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true)); - withCond.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "if", _scxml, true)); - withCond.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "elseif", _scxml, true)); - for (int i = 0; i < withCond.size(); i++) { - Element elem = Element(withCond[i]); - if (HAS_ATTR(elem, "cond")) { - std::string code = ATTR(elem, "cond"); - code = sanitizeCode(code); - elem.setAttribute("cond", code); - allCode.insert(code); - } - } - } - { - NodeSet withExpr; - withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "log", _scxml, true)); - withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "data", _scxml, true)); - withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "assign", _scxml, true)); - withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "content", _scxml, true)); - withExpr.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "param", _scxml, true)); - for (int i = 0; i < withExpr.size(); i++) { - Element elem = Element(withExpr[i]); - if (HAS_ATTR(elem, "expr")) { - std::string code = ATTR(elem, "expr"); - code = sanitizeCode(code); - elem.setAttribute("expr", code); - allCode.insert(code); - } - } - } - { - NodeSet withLocation; - withLocation.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "assign", _scxml, true)); - for (int i = 0; i < withLocation.size(); i++) { - Element elem = Element(withLocation[i]); - if (HAS_ATTR(elem, "location")) { - std::string code = ATTR(elem, "location"); - code = sanitizeCode(code); - elem.setAttribute("location", code); - allCode.insert(code); - } - } - } - { - NodeSet withText; - withText.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml, true)); - withText.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "data", _scxml, true)); - for (int i = 0; i < withText.size(); i++) { - NodeSet texts = filterChildType(Node_base::TEXT_NODE, withText[i], true); - for (int j = 0; j < texts.size(); j++) { - if (texts[j].getNodeValue().size() > 0) { - Text elem = Text(texts[j]); - std::string code = elem.getNodeValue(); - code = sanitizeCode(code); - elem.setNodeValue(code); - allCode.insert(code); - } - } - } - } - for (std::set::const_iterator codeIter = allCode.begin(); codeIter != allCode.end(); codeIter++) { - _analyzer.addCode(*codeIter); - } - -} - -std::string FSMToPromela::sanitizeCode(const std::string& code) { - std::string replaced = code; - boost::replace_all(replaced, "\"", "'"); - boost::replace_all(replaced, "_sessionid", "_SESSIONID"); - boost::replace_all(replaced, "_name", "_NAME"); - return replaced; -} - -void PromelaInline::dump() { - std::list >::iterator outerIter = sequences.begin(); - while(outerIter != sequences.end()) { - std::list::iterator innerIter = outerIter->begin(); - while(innerIter != outerIter->end()) { - std::cout << *innerIter << " "; - innerIter++; - } - std::cout << std::endl; - outerIter++; - } -} - -void FSMToPromela::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; - } - -// std::cerr << _scxml << std::endl; - - initNodes(); - - writeEvents(stream); - stream << std::endl; - writeStates(stream); - stream << std::endl; - if (_analyzer.usesInPredicate()) { - writeStateMap(stream); - stream << std::endl; - } - writeTypeDefs(stream); - stream << std::endl; - writeStrings(stream); - stream << std::endl; - writeDeclarations(stream); - stream << std::endl; - writeEventSources(stream); - stream << std::endl; - writeFSM(stream); - stream << std::endl; - writeMain(stream); - stream << std::endl; - - // write ltl expression for success - std::stringstream acceptingStates; - std::string seperator; - for (int i = 0; i < _globalStates.size(); i++) { - FlatStateIdentifier flatId(ATTR_CAST(_globalStates[i], "id")); - if (std::find(flatId.getActive().begin(), flatId.getActive().end(), "pass") != flatId.getActive().end()) { - acceptingStates << seperator << "s == s" << i; - seperator = " || "; - } - } - if (acceptingStates.str().size() > 0) { - stream << "ltl { eventually (" << acceptingStates.str() << ") }" << std::endl; - } - -// if (_states.find("active:{pass}") != _states.end()) { -// for (int i = 0; i < _globalStates.size(); i++) { -// if (_states["active:{pass}"] != _globalStates[i]) -// continue; -// stream << "ltl { eventually (s == s" << i << ") }"; -// break; -// } -// } -} - -} \ No newline at end of file diff --git a/src/uscxml/transform/FSMToPromela.h b/src/uscxml/transform/FSMToPromela.h deleted file mode 100644 index 3a9e263..0000000 --- a/src/uscxml/transform/FSMToPromela.h +++ /dev/null @@ -1,272 +0,0 @@ -/** - * @file - * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see . - * @endcond - */ - -#ifndef FSMTOPROMELA_H_RP48RFDJ -#define FSMTOPROMELA_H_RP48RFDJ - -#include "uscxml/interpreter/InterpreterDraft6.h" -#include "uscxml/DOMUtils.h" -#include "uscxml/util/Trie.h" - -#include -#include -#include -#include - -namespace uscxml { - -class USCXML_API PromelaInline { -public: - PromelaInline() : type(PROMELA_NIL) {} - - operator bool() { - return (type != PROMELA_NIL); - } - - void dump(); - - enum PromelaInlineType { - PROMELA_NIL, - PROMELA_CODE, - PROMELA_EVENT_SOURCE, - PROMELA_EVENT_SOURCE_CUSTOM, - PROMELA_PROGRESS_LABEL, - PROMELA_ACCEPT_LABEL, - PROMELA_END_LABEL - }; - - std::string content; - std::list > sequences; - - PromelaInlineType type; -}; - -class USCXML_API PromelaInlines { -public: - PromelaInlines() : progressLabels(0), acceptLabels(0), endLabels(0), eventSources(0), customEventSources(0), codes(0) {} - - void merge(const PromelaInlines& other) { - inlines.insert(inlines.end(), other.inlines.begin(), other.inlines.end()); - progressLabels += other.progressLabels; - acceptLabels += other.acceptLabels; - endLabels += other.endLabels; - eventSources += other.eventSources; - customEventSources += other.customEventSources; - codes += other.codes; - } - - operator bool() { - return inlines.size() > 0; - } - - std::list inlines; - int progressLabels; - int acceptLabels; - int endLabels; - int eventSources; - int customEventSources; - int codes; -}; - -class USCXML_API PromelaCodeAnalyzer { -public: - class PromelaTypedef { - public: - PromelaTypedef() : arraySize(0) {} - std::string name; - std::string type; - size_t arraySize; - std::map types; - - bool operator==(const PromelaTypedef& other) const { - return name == other.name; - } - - }; - - PromelaCodeAnalyzer() : _eventTrie("."), _lastStrIndex(1), _lastStateIndex(0), _lastEventIndex(1), _usesInPredicate(false), _usesPlatformVars(false) { - } - - void addCode(const std::string& code); - void addEvent(const std::string& eventName); - void addState(const std::string& stateName); - void addLiteral(const std::string& stateName, int forceIndex = -1); - - bool usesComplexEventStruct() { - return _typeDefs.types.find("_event") != _typeDefs.types.end(); - } - bool usesEventField(const std::string& fieldName) { - if (usesComplexEventStruct() && _typeDefs.types["_event"].types.find(fieldName) != _typeDefs.types["_event"].types.end()) - return true; - return false; - } - - bool usesInPredicate() { - return _usesInPredicate; - } - bool usesPlatformVars() { - return _usesPlatformVars; - } - - std::string macroForLiteral(const std::string& literal); - int indexForLiteral(const std::string& literal); - int arrayIndexForOrigState(const std::string& stateName); - - std::set getLiterals() { - return _strLiterals; - } - std::set getEventsWithPrefix(const std::string& prefix); - std::map& getEvents() { - return _events; - } - - std::map& getStates() { - return _states; - } - - std::map& getOrigStates() { - return _origStateIndex; - } - - std::list& getOrigStates(const std::string& state) { - if (_origStateMap.find(state) == _origStateMap.end()) - throw std::runtime_error("No original states known for " + state); - return _origStateMap[state]; - } - - Trie& getTrie() { - return _eventTrie; - } - - std::string replaceLiterals(const std::string code); - - PromelaTypedef getTypes() { - return _typeDefs; - } - -protected: - std::string createMacroName(const std::string& literal); - int enumerateLiteral(const std::string& literal, int forceIndex = -1); - - std::set _strLiterals; // all string literals - std::map _strMacroNames; // macronames for string literals - std::map _strIndex; // integer enumeration for string - std::map _origStateIndex; // state enumeration for original states - - std::map _states; - std::map > _origStateMap; // states from the original state chart - std::map _events; - - PromelaTypedef _typeDefs; - - Trie _eventTrie; - -private: - std::set _macroNameSet; // helper set for uniqueness of macros - int _lastStrIndex; - int _lastStateIndex; - int _lastEventIndex; - bool _usesInPredicate; - bool _usesPlatformVars; -}; - -class USCXML_API PromelaEventSource { -public: - - enum PromelaEventSourceType { - PROMELA_EVENT_SOURCE_INVALID, - PROMELA_EVENT_SOURCE_INVOKER, - PROMELA_EVENT_SOURCE_GLOBAL, - }; - - PromelaEventSource(); - PromelaEventSource(const PromelaInlines& sources, const Arabica::DOM::Node& parent); - - void writeStartEventSources(std::ostream& stream, int indent = 0); - void writeStopEventSources(std::ostream& stream, int indent = 0); - void writeDeclarations(std::ostream& stream, int indent = 0); - void writeEventSource(std::ostream& stream); - - operator bool() { - return type != PROMELA_EVENT_SOURCE_INVALID; - } - - std::string name; - PromelaInlines eventSources; - Arabica::DOM::Node container; - PromelaEventSourceType type; - PromelaCodeAnalyzer* analyzer; -}; - -class USCXML_API FSMToPromela : public InterpreterDraft6 { -public: - static void writeProgram(std::ostream& stream, - const Interpreter& interpreter); - - static std::string beautifyIndentation(const std::string& code, int indent = 0); - -protected: - FSMToPromela(); - void writeProgram(std::ostream& stream); - - void initNodes(); - - void writeEvents(std::ostream& stream); - void writeStates(std::ostream& stream); - void writeStateMap(std::ostream& stream); - void writeTypeDefs(std::ostream& stream); - void writeStrings(std::ostream& stream); - void writeDeclarations(std::ostream& stream); - void writeEventSources(std::ostream& stream); - void writeExecutableContent(std::ostream& stream, const Arabica::DOM::Node& node, int indent = 0); - void writeInlineComment(std::ostream& stream, const Arabica::DOM::Node& 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& condChain, int indent = 0); - void writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent = 0); - - Arabica::XPath::NodeSet getTransientContent(const Arabica::DOM::Element& state, const std::string& source = ""); - Arabica::DOM::Node getUltimateTarget(const Arabica::DOM::Element& transition); - - static PromelaInlines getInlinePromela(const std::string&); - static PromelaInlines getInlinePromela(const Arabica::XPath::NodeSet& elements, bool recurse = false); - static PromelaInlines getInlinePromela(const Arabica::DOM::Node& elements); - -// std::string replaceStringsInExpression(const std::string& expr); - - std::string sanitizeCode(const std::string& code); - - Arabica::XPath::NodeSet _globalStates; - Arabica::DOM::Node _startState; - std::map > _states; - std::map, int> _transitions; - - std::list _varInitializers; - - PromelaCodeAnalyzer _analyzer; - - std::map _invokers; - PromelaEventSource _globalEventSource; -}; - -} - -#endif /* end of include guard: FSMTOPROMELA_H_RP48RFDJ */ diff --git a/src/uscxml/transform/FlatStateIdentifier.h b/src/uscxml/transform/FlatStateIdentifier.h index 5cbd5f2..92e6e8a 100644 --- a/src/uscxml/transform/FlatStateIdentifier.h +++ b/src/uscxml/transform/FlatStateIdentifier.h @@ -88,6 +88,13 @@ public: return tmp.getStateId(); } + static std::string toStateId(const Arabica::XPath::NodeSet activeStates, + const Arabica::XPath::NodeSet alreadyEnteredStates = Arabica::XPath::NodeSet(), + const std::map > historyStates = std::map >()) { + FlatStateIdentifier tmp(activeStates, alreadyEnteredStates, historyStates); + return tmp.getStateId(); + } + FlatStateIdentifier(const std::string& identifier) : stateId(identifier) { std::string parsedName; // parse unique state identifier diff --git a/src/uscxml/transform/Transformer.cpp b/src/uscxml/transform/Transformer.cpp new file mode 100644 index 0000000..fd4920a --- /dev/null +++ b/src/uscxml/transform/Transformer.cpp @@ -0,0 +1,20 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "Transformer.h" \ No newline at end of file diff --git a/src/uscxml/transform/Transformer.h b/src/uscxml/transform/Transformer.h new file mode 100644 index 0000000..8ea19d9 --- /dev/null +++ b/src/uscxml/transform/Transformer.h @@ -0,0 +1,79 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef TRANSFORMER_H_32113356 +#define TRANSFORMER_H_32113356 + +#include "uscxml/interpreter/InterpreterRC.h" +#include + +namespace uscxml { + +class USCXML_API TransformerImpl { +public: + TransformerImpl() {} + + virtual void writeTo(std::ostream& stream) = 0; + virtual operator Interpreter() { + throw std::runtime_error("Transformer cannot be interpreted as an Interpreter again"); + } + +}; + +class USCXML_API Transformer { +public: +// Transformer(const Interpreter& source) { _impl = new (source) } + + Transformer() : _impl() {} // the empty, invalid interpreter + Transformer(boost::shared_ptr const impl) : _impl(impl) { } + Transformer(const Transformer& other) : _impl(other._impl) { } + virtual ~Transformer() {}; + + operator bool() const { + return (_impl); + } + bool operator< (const Transformer& other) const { + return _impl < other._impl; + } + bool operator==(const Transformer& other) const { + return _impl == other._impl; + } + bool operator!=(const Transformer& other) const { + return _impl != other._impl; + } + Transformer& operator= (const Transformer& other) { + _impl = other._impl; + return *this; + } + + virtual void writeTo(std::ostream& stream) { + _impl->writeTo(stream); + } + operator Interpreter() { + return _impl->operator Interpreter(); + } + +protected: + boost::shared_ptr _impl; + +}; + +} + +#endif /* end of include guard: TRANSFORMER_H_32113356 */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 035f22e..8aa3fb5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,6 +25,7 @@ if (NOT WIN32) ARGS ${CMAKE_CURRENT_SOURCE_DIR}/uscxml/arabica/test-arabica-events.xml) USCXML_TEST_COMPILE(NAME test-arabica-parsing LABEL general/test-arabica-parsing FILES src/test-arabica-parsing.cpp) USCXML_TEST_COMPILE(NAME test-promela-parser LABEL general/test-promela-parser FILES src/test-promela-parser.cpp) + target_link_libraries(test-promela-parser uscxml_transform) USCXML_TEST_COMPILE(BUILD_ONLY NAME test-stress LABEL general/test-stress FILES src/test-stress.cpp) endif() @@ -73,7 +74,7 @@ find_program(GCC gcc) if (NOT BUILD_MINIMAL) add_executable(test-w3c src/test-w3c.cpp) - target_link_libraries(test-w3c uscxml) + target_link_libraries(test-w3c uscxml uscxml_transform) set_target_properties(test-w3c PROPERTIES FOLDER "Tests") file(GLOB_RECURSE W3C_TESTS diff --git a/test/src/test-promela-parser.cpp b/test/src/test-promela-parser.cpp index 261b8c9..11333f1 100644 --- a/test/src/test-promela-parser.cpp +++ b/test/src/test-promela-parser.cpp @@ -4,7 +4,7 @@ #include "uscxml/Interpreter.h" #include "uscxml/plugins/datamodel/promela/PromelaDataModel.h" #include "uscxml/plugins/datamodel/promela/PromelaParser.h" -#include "uscxml/transform/FSMToPromela.h" +#include "uscxml/transform/ChartToPromela.h" #include #include @@ -21,7 +21,7 @@ void testInlinePromela() { #promela-inline:\n \ This is foo!\ "; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 0 && prmInls.codes == 1 && prmInls.customEventSources == 0 && @@ -35,7 +35,7 @@ void testInlinePromela() { { std::string test = "#promela-progress"; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 0 && prmInls.codes == 0 && prmInls.customEventSources == 0 && @@ -48,7 +48,7 @@ void testInlinePromela() { { std::string test = "#promela-accept and then some"; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 1 && prmInls.codes == 0 && prmInls.customEventSources == 0 && @@ -61,7 +61,7 @@ void testInlinePromela() { { std::string test = "#promela-end and then some"; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 0 && prmInls.codes == 0 && prmInls.customEventSources == 0 && @@ -77,7 +77,7 @@ void testInlinePromela() { #promela-event-source:\n \ This is foo!\ "; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 0 && prmInls.codes == 0 && prmInls.customEventSources == 0 && @@ -103,7 +103,7 @@ void testInlinePromela() { This is foo!\n \ This is bar!\n \ "; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 0 && prmInls.codes == 0 && prmInls.customEventSources == 0 && @@ -134,7 +134,7 @@ void testInlinePromela() { #promela-event-source-custom:\n \ This is foo!\ "; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 0 && prmInls.codes == 0 && prmInls.customEventSources == 1 && @@ -153,7 +153,7 @@ void testInlinePromela() { This is foo! \n\ #promela-progress\ "; - PromelaInlines prmInls = FSMToPromela::getInlinePromela(test); + PromelaInlines prmInls = ChartToPromela::getInlinePromela(test); assert(prmInls.acceptLabels == 0 && prmInls.codes == 0 && prmInls.customEventSources == 1 && diff --git a/test/src/test-w3c.cpp b/test/src/test-w3c.cpp index 27c69b2..b31eb72 100644 --- a/test/src/test-w3c.cpp +++ b/test/src/test-w3c.cpp @@ -5,7 +5,7 @@ #include "uscxml/Factory.h" #include "uscxml/server/HTTPServer.h" -#include "uscxml/transform/ChartToFSM.h" +#include "uscxml/transform/ChartToFlatSCXML.h" #include #include @@ -136,9 +136,10 @@ int main(int argc, char** argv) { Interpreter interpreter; LOG(INFO) << "Processing " << documentURI << (withFlattening ? " FSM converted" : "") << (delayFactor ? "" : " with delays *= " + toStr(delayFactor)); if (withFlattening) { - Interpreter flatInterpreter = Interpreter::fromURI(documentURI); - interpreter = Interpreter::fromDOM(ChartToFSM::flatten(flatInterpreter).getDocument(), flatInterpreter.getNameSpaceInfo()); - interpreter.setSourceURI(flatInterpreter.getSourceURI()); + interpreter = Interpreter::fromURI(documentURI); + Transformer flattener = ChartToFlatSCXML::transform(interpreter); + interpreter = flattener; +// std::cout << interpreter.getDocument() << std::endl; } else { interpreter = Interpreter::fromURI(documentURI); } diff --git a/test/w3c/analyze_promela_tests.pl b/test/w3c/analyze_promela_tests.pl new file mode 100755 index 0000000..5e1b96b --- /dev/null +++ b/test/w3c/analyze_promela_tests.pl @@ -0,0 +1,167 @@ +#!/usr/bin/perl -w + +use strict; +use File::Spec; +use File::Basename; +use Data::Dumper; + +my $toBaseDir = File::Spec->canonpath(dirname($0)); +my $outDir = File::Spec->catfile($toBaseDir, 'graphs'); +my $testResultFile; + +my @dataQuery; + +# iterate given arguments and populate $dataQuery +foreach my $argnum (0 .. $#ARGV) { + if ($argnum == $#ARGV) { + if (-f $ARGV[$argnum]) { + $testResultFile = $ARGV[$argnum]; + last; + } + if (-f File::Spec->catfile($toBaseDir, $ARGV[$argnum])) { + $testResultFile = File::Spec->catfile($toBaseDir, $ARGV[$argnum]); + last; + } + } + push(@dataQuery, \[split('\.', $ARGV[$argnum])]); +} + +if (!$testResultFile) { + $testResultFile = File::Spec->catfile($toBaseDir, "../../build/cli/Testing/Temporary/LastTest.log"); +} + +open(FILE, $testResultFile) or die $!; +mkdir($outDir) or die($!) if (! -d $outDir); + +my $test; +my $block; +my $currTest; +my $testIndex = 0; + +$/ = '-' x 58 . "\n"; + +while ($block = ) { + chomp($block); + + # Test Preambel ======== + if ($block =~ + / + \n? + (\d+)\/(\d+)\sTesting:\s(.*)\n + (\d+)\/(\d+)\sTest:\s(.*)\n + /x ) { + $currTest = $3; + $test->{$currTest}->{'name'} = $currTest; + $test->{$currTest}->{'number'} = $1; + $test->{$currTest}->{'total'} = $2; + $test->{$currTest}->{'index'} = $testIndex++; + + if ($currTest =~ /test(\d+\w?)\.scxml$/) { + $test->{$currTest}->{'w3c'} = $1; + } + + next; + } + + # Test Epilog ======== + if ($block =~ + / + Test\s(\S+)\sReason:\n + /x ) { + $test->{$currTest}->{'status'} = lc($1); + next; + } + + # Promela Test ======== + if ($block =~ + / + Approximate\sComplexity:\s(\d+)\n + Actual\sComplexity:\s(\d+)\n + /x ) { + $test->{$currTest}->{'flat'}->{'apprComplexity'} = $1; + $test->{$currTest}->{'flat'}->{'actualComplexity'} = $2; + + if ($block =~ /State-vector (\d+) byte, depth reached (\d+), errors: (\d+)/) { + $test->{$currTest}->{'pml'}->{'states'}->{'stateSize'} = $1; + $test->{$currTest}->{'pml'}->{'states'}->{'depth'} = $2; + $test->{$currTest}->{'pml'}->{'states'}->{'errors'} = $3; + } + if ($block =~ + / + \s+(\d+)\sstates,\sstored\s\((\d+)\svisited\)\n + \s+(\d+)\sstates,\smatched\n + \s+(\d+)\stransitions\s\(=\svisited\+matched\)\n + \s+(\d+)\satomic\ssteps\n + hash\sconflicts:\s+(\d+)\s\(resolved\) + /x ) { + $test->{$currTest}->{'pml'}->{'states'}->{'stateStored'} = $1; + $test->{$currTest}->{'pml'}->{'states'}->{'stateVisited'} = $2; + $test->{$currTest}->{'pml'}->{'states'}->{'stateMatched'} = $3; + $test->{$currTest}->{'pml'}->{'states'}->{'transitions'} = $4; + $test->{$currTest}->{'pml'}->{'states'}->{'atomicSteps'} = $5; + $test->{$currTest}->{'pml'}->{'hashConflicts'} = $6; + } + + if ($block =~ + / + \s+([\d\.]+)\sequivalent\smemory\susage\sfor\sstates.* + \s+([\d\.]+)\sactual\smemory\susage\sfor\sstates\n + \s+([\d\.]+)\smemory\sused\sfor\shash\stable\s\(-w(\d+)\)\n + \s+([\d\.]+)\smemory\sused\sfor\sDFS\sstack\s\(-m(\d+)\) + \s+([\d\.]+)\stotal\sactual\smemory\susage + /x ) { + $test->{$currTest}->{'pml'}->{'memory'}->{'states'} = $1; + $test->{$currTest}->{'pml'}->{'memory'}->{'actual'} = $2; + $test->{$currTest}->{'pml'}->{'memory'}->{'hashTable'} = $3; + $test->{$currTest}->{'pml'}->{'memory'}->{'hashLimit'} = $4; + $test->{$currTest}->{'pml'}->{'memory'}->{'dfsStack'} = $5; + $test->{$currTest}->{'pml'}->{'memory'}->{'dfsLimit'} = $6; + $test->{$currTest}->{'pml'}->{'memory'}->{'total'} = $7; + } + + next; + } + +} +close(FILE); + +if (@dataQuery > 0) { + while (my ($name, $data) = each $test) { + my $seperator = ""; + foreach (@dataQuery) { + my $currVal = $data; + my @query = @${$_}; + foreach (@query) { + my $dataKey = $_; + if (defined($currVal->{$dataKey})) { + $currVal = $currVal->{$dataKey}; + } else { + die("no key $dataKey in structure:\n" . Dumper($currVal)); + } + } + print $seperator . $currVal; + $seperator = ", "; + } + print "\n"; + } +} else { + while (my ($name, $data) = each $test) { + # get one $data into scope + print "Available Queries:\n"; + + sub dumpQueries() { + my $structure = shift; + my $path = shift || ""; + while (my ($name, $data) = each $structure) { + if (ref(\$data) eq "SCALAR") { + print "\t" . $path . $name . "\n"; + } else { + &dumpQueries($data, $path . $name . "."); + } + } + } + &dumpQueries($data); + exit; + } +} + diff --git a/test/w3c/graphs/data/pml_states.data b/test/w3c/graphs/data/pml_states.data new file mode 100644 index 0000000..7381008 --- /dev/null +++ b/test/w3c/graphs/data/pml_states.data @@ -0,0 +1,84 @@ +147, 108, 0.281, 3 +505, 116, 0.276, 6 +348, 108, 0.284, 3 +349, 204, 0.280, 4 +319, 108, 0.283, 3 +527, 196, 0.278, 5 +337, 196, 0.283, 3 +310, 108, 0.283, 5 +404, 108, 0.272, 10 +405, 108, 0.265, 13 +183, 196, 0.283, 3 +533, 124, 0.270, 9 +407, 108, 0.282, 4 +372, 108, 0.283, 4 +413, 108, 0.280, 11 +335, 196, 0.283, 3 +193, 108, 0.280, 4 +550, 108, 0.284, 4 +303, 108, 0.284, 3 +252, 108, 0.276, 8 +387, 108, 0.276, 18 +333, 196, 0.283, 3 +278, 108, 0.284, 4 +529, 196, 0.278, 5 +189, 108, 0.284, 3 +554, 108, 0.284, 5 +504, 124, 0.267, 11 +576, 108, 0.284, 13 +336, 108, 0.280, 4 +409, 108, 0.275, 6 +158, 108, 0.281, 4 +501, 108, 0.284, 3 +205, 204, 0.280, 4 +419, 108, 0.284, 3 +174, 108, 0.284, 3 +250, 100, 0.286, 6 +200, 108, 0.284, 3 +570, 108, 0.259, 10 +186, 204, 0.280, 4 +201, 108, 0.284, 3 +339, 196, 0.283, 3 +421, 108, 0.280, 5 +579, 108, 0.262, 8 +176, 204, 0.280, 4 +237, 108, 0.282, 7 +242, 108, 0.276, 9 +378, 108, 0.282, 4 +412, 108, 0.262, 8 +288, 108, 0.284, 3 +375, 108, 0.281, 4 +396, 112, 0.283, 3 +423, 108, 0.280, 4 +155, 124, 0.283, 3 +198, 204, 0.283, 3 +551, 116, 0.283, 4 +355, 108, 0.285, 4 +403c, 108, 0.279, 9 +416, 108, 0.279, 6 +417, 108, 0.279, 10 +330, 204, 0.279, 4 +376, 108, 0.284, 3 +506, 116, 0.274, 7 +187, 108, 0.284, 6 +318, 124, 0.281, 4 +149, 108, 0.282, 3 +364, 108, 0.279, 29 +173, 108, 0.284, 3 +279, 108, 0.284, 4 +495, 108, 0.280, 4 +287, 108, 0.284, 3 +406, 108, 0.267, 10 +323, 108, 0.284, 3 +553, 108, 0.284, 3 +153, 132, 0.282, 3 +190, 116, 0.280, 4 +377, 108, 0.278, 5 +403b, 108, 0.282, 6 +503, 116, 0.277, 5 +179, 196, 0.283, 3 +321, 108, 0.284, 3 +148, 108, 0.282, 3 +411, 108, 0.280, 4 +403a, 108, 0.275, 5 +144, 108, 0.281, 4 diff --git a/test/w3c/graphs/promela-states.sh b/test/w3c/graphs/promela-states.sh new file mode 100755 index 0000000..0cb7589 --- /dev/null +++ b/test/w3c/graphs/promela-states.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +ME=`basename $0` +DIR="$( cd "$( dirname "$0" )" && pwd )" + +mkdir ${DIR}/data &> /dev/null + +${DIR}/../analyze_promela_tests.pl w3c pml.states.stateSize pml.memory.actual flat.actualComplexity > ${DIR}/data/pml_states.data + +cat ${DIR}/data/pml_states.data | gnuplot -p -e 'plot "-" with lines,"-" with lines' \ No newline at end of file diff --git a/test/w3c/graphs/run_gnuplot.sh b/test/w3c/graphs/run_gnuplot.sh new file mode 100755 index 0000000..26fe407 --- /dev/null +++ b/test/w3c/graphs/run_gnuplot.sh @@ -0,0 +1,2 @@ +#!/bin/bash +gnuplot test.plot > test.pdf diff --git a/test/w3c/run_promela_test.cmake b/test/w3c/run_promela_test.cmake index e46ab60..1200442 100644 --- a/test/w3c/run_promela_test.cmake +++ b/test/w3c/run_promela_test.cmake @@ -2,7 +2,7 @@ get_filename_component(TEST_FILE_NAME ${TESTFILE} NAME) -execute_process(COMMAND ${USCXML_TRANSFORM_BIN} -s -i ${TESTFILE} -o ${OUTDIR}/${TEST_FILE_NAME}.pml RESULT_VARIABLE CMD_RESULT) +execute_process(COMMAND ${USCXML_TRANSFORM_BIN} -i ${TESTFILE} -o ${OUTDIR}/${TEST_FILE_NAME}.pml RESULT_VARIABLE CMD_RESULT) if(CMD_RESULT) message(FATAL_ERROR "Error running ${USCXML_TRANSFORM_BIN}: ${CMD_RESULT}") endif() -- cgit v0.12