/** * @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; // } // } } }