From c2188b1ff4bdbb5a6174ed7d73637c1c7e67aec5 Mon Sep 17 00:00:00 2001 From: xiongyee2000 Date: Sat, 9 Jun 2018 15:42:08 +0800 Subject: [PATCH 1/7] Added scxml2cpp.py. --- tools/scxml_converter/scxml2cpp.py | 61 +++++++++++++++++++++++++++++ tools/scxml_converter/stopwatch.xml | 17 ++++++++ 2 files changed, 78 insertions(+) create mode 100755 tools/scxml_converter/scxml2cpp.py create mode 100644 tools/scxml_converter/stopwatch.xml diff --git a/tools/scxml_converter/scxml2cpp.py b/tools/scxml_converter/scxml2cpp.py new file mode 100755 index 0000000..35e6e2f --- /dev/null +++ b/tools/scxml_converter/scxml2cpp.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import getopt +import sys +import os + +from xml.dom.minidom import parse +import xml.dom.minidom + +def main(argv): + scxmlFilePath = None + outputDir = None + namespace = None + + usage = ''' +Usage: + [options] + +scxmlFile: The scxml file path +outputDir: The output directory path after conversion + +options: +-n, --ns: The base namespace of the classes converted to. +-h, --help: Usage +''' + + if len(sys.argv) < 3: + print usage + return -1 + scxmlFilePath = sys.argv[1] + outputDir = sys.argv[2] + + try: + opts, args = getopt.getopt(sys.argv[3:], "n:h", ["ns=", "help"]) + for a,o in opts: + if a in ('-n', '--ns'): + namespace = str(o) + elif a in ('-h', '--help'): + print usage + return 0 + except: + print "Invalid argument(s)." + print usage + return -2 + + if (not os.path.exists(scxmlFilePath)): + print "scxmlFile does not exist." + return 1 + + domTree = xml.dom.minidom.parse(scxmlFilePath) + rootElement = domTree.documentElement + + for node in rootElement.childNodes: + if node.nodeType == node.ELEMENT_NODE and node.tagName == 'state': + print node.getAttribute('id') + +pass + +if __name__ == "__main__": + main(sys.argv) \ No newline at end of file diff --git a/tools/scxml_converter/stopwatch.xml b/tools/scxml_converter/stopwatch.xml new file mode 100644 index 0000000..3acc80f --- /dev/null +++ b/tools/scxml_converter/stopwatch.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file From 0002193598a57b6e02297c00de4b4bb6bd26c904 Mon Sep 17 00:00:00 2001 From: xiongyee2000 Date: Sun, 10 Jun 2018 22:13:46 +0800 Subject: [PATCH 2/7] Continued scxml2cpp.py... --- tools/scxml_converter/scxml2cpp.py | 101 +++++++++++++++++++++++++++-- unit_test/src/CMakeLists.txt | 4 +- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/tools/scxml_converter/scxml2cpp.py b/tools/scxml_converter/scxml2cpp.py index 35e6e2f..a8a7399 100755 --- a/tools/scxml_converter/scxml2cpp.py +++ b/tools/scxml_converter/scxml2cpp.py @@ -8,10 +8,56 @@ from xml.dom.minidom import parse import xml.dom.minidom +def getFirstLevelElementsByTagName(node, tagName): + elementList = [] + for childNode in node.childNodes: + if childNode.nodeType == childNode.ELEMENT_NODE and childNode.tagName == tagName: + elementList.append(childNode) + return elementList +pass + +def isSubMachine(stateElement): + return False +pass + +def getTransitionElements(stateElement): + return [] +pass + +header_file_begin = ''' +#ifndef $HEADER_MACRO +#define $HEADER_MACRO + +#include +#include +#include "org/jinsha/imachine/StateMachine.h" + +using namespace org::jinsha::imachine; + +class $className : public StateMachine { +public: +''' + +header_file_end = ''' + ATMStateMachine(int id); + virtual ~ATMStateMachine(); + +private: + ATMStateMachine(); + ATMStateMachine(const ATMStateMachine& instance); + ATMStateMachine& operator = (const ATMStateMachine& instance); + + std::list subMachines; +}; + +#endif +''' + def main(argv): scxmlFilePath = None outputDir = None namespace = None + className = 'NoNameStateMachine' usage = ''' Usage: @@ -21,8 +67,9 @@ def main(argv): outputDir: The output directory path after conversion options: --n, --ns: The base namespace of the classes converted to. --h, --help: Usage +-n, --ns: The base namespace of the classes converted to. +-c, --className: The state machine class name after conversion. +-h, --help: Usage ''' if len(sys.argv) < 3: @@ -30,15 +77,20 @@ def main(argv): return -1 scxmlFilePath = sys.argv[1] outputDir = sys.argv[2] + if outputDir.endswith('/') or outputDir.endswith('\\'): + outputDir = outputDir[:-1] try: - opts, args = getopt.getopt(sys.argv[3:], "n:h", ["ns=", "help"]) + opts, args = getopt.getopt(sys.argv[3:], "n:c:h", ["ns=", "className=", "help"]) for a,o in opts: if a in ('-n', '--ns'): namespace = str(o) elif a in ('-h', '--help'): print usage return 0 + elif a in ('-c', '--className'): + className = str(o) + className = className.replace(' ', '') except: print "Invalid argument(s)." print usage @@ -47,13 +99,48 @@ def main(argv): if (not os.path.exists(scxmlFilePath)): print "scxmlFile does not exist." return 1 - + domTree = xml.dom.minidom.parse(scxmlFilePath) rootElement = domTree.documentElement + initStateId = None + + if rootElement.nodeType == rootElement.ELEMENT_NODE and rootElement.tagName == 'scxml': + initStateId = rootElement.getAttribute('initial') + else: + print 'This is not a scxml file.' + return 2 + + if (not os.path.exists(outputDir)): + os.mkdir(outputDir, 0755) + if (not os.path.exists(outputDir)): + print "Failed to create outputDir: " + outputDir + return 3 + + outputHeaderFileName = outputDir + '/' + className + '.h'; + outputCppFileName = outputDir + '/' + className + '.cpp'; + outputHeaderFile = open(outputHeaderFileName, 'w') + outputCppFile = open(outputCppFileName, 'w') + headerMacro = className.upper() + '_H_' + global header_file_begin + header_file_begin = header_file_begin.replace('$HEADER_MACRO', headerMacro) + header_file_begin = header_file_begin.replace('$className', className) + + outputHeaderFile.write(header_file_begin) - for node in rootElement.childNodes: - if node.nodeType == node.ELEMENT_NODE and node.tagName == 'state': - print node.getAttribute('id') + stateElementList = getFirstLevelElementsByTagName(rootElement, 'state') + index = 0 + for stateElement in stateElementList: + index += 1 + id = stateElement.getAttribute('id') + if id == None: + id = str(index) + #transitionElementList = getTransitionElements(stateElement) + if isSubMachine(stateElement): + a = 1 + #createSubMachine(stateElement); + outputHeaderFile.write(' static const int STATE_%s = %d;\n' % (id, index)) + + outputHeaderFile.write(header_file_end) pass diff --git a/unit_test/src/CMakeLists.txt b/unit_test/src/CMakeLists.txt index fefcdb5..6cd6e9e 100644 --- a/unit_test/src/CMakeLists.txt +++ b/unit_test/src/CMakeLists.txt @@ -11,8 +11,8 @@ set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) set(GTEST_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googletest/include) set(GMOCK_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googlemock/include) -set(GMOCK_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googlemock) -set(GTEST_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googlemock/gtest) +set(GMOCK_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/build/googlemock) +set(GTEST_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/build/googlemock/gtest) add_definitions(-std=c++11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") From 473b3b42ed56617e8cb64d84d16d462bff4b3c88 Mon Sep 17 00:00:00 2001 From: xiongyee2000 Date: Sun, 17 Jun 2018 16:25:40 +0800 Subject: [PATCH 3/7] Resolved UT build problem. --- unit_test/src/CMakeLists.txt | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/unit_test/src/CMakeLists.txt b/unit_test/src/CMakeLists.txt index fefcdb5..be1c860 100644 --- a/unit_test/src/CMakeLists.txt +++ b/unit_test/src/CMakeLists.txt @@ -11,34 +11,40 @@ set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) set(GTEST_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googletest/include) set(GMOCK_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googlemock/include) -set(GMOCK_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googlemock) -set(GTEST_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/googlemock/gtest) +set(GMOCK_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/build/googlemock) +set(GTEST_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdParty/gtest-1.8.0/build/googlemock/gtest) add_definitions(-std=c++11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") -set(src_dir ) -aux_source_directory(. src_dir) +set(ut_src_files ) +aux_source_directory(. ut_src_files) + +set(imachine_root_dir ${CMAKE_CURRENT_SOURCE_DIR}/../..) +aux_source_directory(${imachine_root_dir}/src/org/jinsha/imachine imachine_src_files) add_definitions(-DMOCK_UT) -include_directories(${GTEST_INCLUDE_PATH}) +include_directories(${GTEST_INCLUDE_PATH} ) include_directories(${GMOCK_INCLUDE_PATH}) +include_directories(${imachine_root_dir}/src) link_directories( ${GTEST_LIB_PATH} ${GMOCK_LIB_PATH} ) -add_executable(imachine_ut ${src_dir} +add_executable(imachine_ut + ${ut_src_files} + ${imachine_src_files} ) target_link_libraries(imachine_ut -libgtest.a -libgtest_main.a -libgmock.a -libgmock_main.a -pthread -gcov + libgtest.a + libgtest_main.a + libgmock.a + libgmock_main.a + pthread + gcov ) From 9c2567a21a56c73ac2bf43f21bdeca4d25ca6045 Mon Sep 17 00:00:00 2001 From: xiongyee2000 Date: Mon, 18 Jun 2018 19:36:58 +0800 Subject: [PATCH 4/7] ... --- tools/scxml_converter/scxml2cpp.py | 272 ++++++++++++++++++++++++++--- 1 file changed, 243 insertions(+), 29 deletions(-) diff --git a/tools/scxml_converter/scxml2cpp.py b/tools/scxml_converter/scxml2cpp.py index a8a7399..9c3ba32 100755 --- a/tools/scxml_converter/scxml2cpp.py +++ b/tools/scxml_converter/scxml2cpp.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# -*- coding: UTF-8 -*- +#coding=utf-8 import getopt import sys @@ -24,7 +24,149 @@ def getTransitionElements(stateElement): return [] pass -header_file_begin = ''' +class State: + def __init__(self, id, name, isInit = False, subState = None): + self.id = id + self.name = name + self.isInit = isInit + self.subState = subState + + self.eventMapList = [] +pass + +class Transition: + def __init__(self, id, name, fromState, toState): + self.id = id + self.name = name + self.fromState = fromState + self.toState = toState +pass + +class Event: + def __init__(self, id, name): + self.id = id + self.name = name +pass + +class StateMachine: + def __init__(self): + self.stateList = [] + self.transitionList = [] + self.eventList = [] + self.initState = None + + def getStateByName(self, name): + for item in self.stateList: + if item.name == name: + return item + return None + + def getEventByName(self, name): + for item in self.eventList: + if item.name == name: + return item + return None + + def build(self, element, isRoot = False): + self.stateList = [] + self.transitionList = [] + self.eventList = [] + + stateElementList = None + firstElement = None + foundInitState = False + + initStateName = element.getAttribute('initial') #try to find initial as attribute + if initStateName == '': #try to find initial as child element + tmpList = getFirstLevelElementsByTagName(element, 'initial') + if len(tmpList) > 0: + initStateName = tmpList[0].firstChild.data + foundInitState = True + else: #Then the initial shall be the first one + tmpNode = element.firstChild + while tmpNode != None: + if tmpNode.nodeType == tmpNode.ELEMENT_NODE and (tmpNode.tagName == 'state' or tmpNode.tagName == 'parallel'): + initStateName = tmpNode.getAttribute('id') + if initStateName == '': #id might not be present according the scxml specification + initStateName = '_@_' + tmpNode.setAttribute('id', '_@_') + foundInitState = True + break + tmpNode = tmpNode.nextSibling + else: + foundInitState = True + + if not foundInitState: + print '[ERROR] No initial state is found.' + return 4 + + stateElementList = getFirstLevelElementsByTagName(element, 'state') +# if isRoot: +# stateElementList = [] +# stateElementList.append(element) +# else: +# stateElementList = getFirstLevelElementsByTagName(element, 'state') + + ################################################################ + #Generate states + ################################################################ + stateCount = 0 + for stateElement in stateElementList: + stateCount += 1 + state = None + stateName = stateElement.getAttribute('id') + if stateName == '': + stateName = str(stateCount) + if isSubMachine(stateElement): + subMachine = StateMachine() + subMachine.build(stateElement) + state = State(stateCount, stateName, subMachine) + else: + state = State(stateCount, stateName) + self.stateList.append(state) + if initStateName == stateName: + self.initState = state + + ################################################################ + #Generate transitions and event maps + ################################################################ + transitionCount = 0 + eventCount = 0 + stateIndex = -1 + for stateElement in stateElementList: + stateIndex += 1 + transitionElementList = getFirstLevelElementsByTagName(stateElement, 'transition') + for transitionElement in transitionElementList: + transitionCount += 1 + targetStateName = transitionElement.getAttribute('target') + if targetStateName == '': + print 'transition without a target not support.' + exit -1 + sourceState = self.getStateByName(self.stateList[stateIndex].name) + targetState = self.getStateByName(targetStateName) + transition = Transition(transitionCount, targetStateName, sourceState, targetState) + self.transitionList.append(transition) + eventName = transitionElement.getAttribute('event') + if eventName != '': + event = self.getEventByName(eventName) + if event == None: + eventCount += 1 + event = Event(eventCount, eventName) + self.eventList.append(event) + sourceState.eventMapList.append({'event':event, 'transition': transition}) + else: #eventless transition + event = Event(-1, eventName) + sourceState.eventMapList.append({'event':event, 'transition': transition}) + +pass + +def buildSubState(stateElement, stateObject): + return +pass + +def main(argv): + + header_file_begin = ''' #ifndef $HEADER_MACRO #define $HEADER_MACRO @@ -35,25 +177,45 @@ def getTransitionElements(stateElement): using namespace org::jinsha::imachine; class $className : public StateMachine { + public: ''' -header_file_end = ''' - ATMStateMachine(int id); - virtual ~ATMStateMachine(); + header_file_end = ''' + $className(int id); + virtual ~$className(); private: - ATMStateMachine(); - ATMStateMachine(const ATMStateMachine& instance); - ATMStateMachine& operator = (const ATMStateMachine& instance); + $className() = delete; + $className(const ATMStateMachine& instance) = delete; + $className& operator = (const ATMStateMachine& instance) = delete; std::list subMachines; }; #endif +''' + + cpp_file_begin = ''' +#include "$className.h" + +$className::$className(int id) : + StateMachine(id, "$className") +{ ''' -def main(argv): + cpp_file_end = ''' +}; + +$className::~$className() +{ + for (auto machine : subMachines) { + delete machine; + } +}; + +''' + scxmlFilePath = None outputDir = None namespace = None @@ -71,7 +233,10 @@ def main(argv): -c, --className: The state machine class name after conversion. -h, --help: Usage ''' - + + ################################################################ + #Process command line arguments + ################################################################ if len(sys.argv) < 3: print usage return -1 @@ -100,13 +265,28 @@ def main(argv): print "scxmlFile does not exist." return 1 + ################################################################ + #Parse input scxml file + ################################################################ domTree = xml.dom.minidom.parse(scxmlFilePath) rootElement = domTree.documentElement - initStateId = None + initStateName = None - if rootElement.nodeType == rootElement.ELEMENT_NODE and rootElement.tagName == 'scxml': - initStateId = rootElement.getAttribute('initial') - else: + #if rootElement.nodeType == rootElement.ELEMENT_NODE and rootElement.tagName == 'scxml': + if rootElement.tagName != 'scxml': +# initStateName = rootElement.getAttribute('initial') +# if initStateName == '': +# tmpNode = rootElement.firstChild +# while tmpNode != None: +# if tmpNode.nodeType == tmpNode.ELEMENT_NODE and (tmpNode.tagName == 'state' or tmpNode.tagName == 'parallel'): +# initStateName = tmpNode.getAttribute('id') +# break +# else: +# tmpNode = tmpNode.nextSibling +# if initStateName == '': +# print '[ERROR] No initial state is found.' +# return 4 +# else: print 'This is not a scxml file.' return 2 @@ -116,31 +296,65 @@ def main(argv): print "Failed to create outputDir: " + outputDir return 3 + rootStateMachine = StateMachine() + rootStateMachine.build(rootElement, True) + outputHeaderFileName = outputDir + '/' + className + '.h'; outputCppFileName = outputDir + '/' + className + '.cpp'; outputHeaderFile = open(outputHeaderFileName, 'w') outputCppFile = open(outputCppFileName, 'w') headerMacro = className.upper() + '_H_' - global header_file_begin header_file_begin = header_file_begin.replace('$HEADER_MACRO', headerMacro) header_file_begin = header_file_begin.replace('$className', className) - outputHeaderFile.write(header_file_begin) - stateElementList = getFirstLevelElementsByTagName(rootElement, 'state') - index = 0 - for stateElement in stateElementList: - index += 1 - id = stateElement.getAttribute('id') - if id == None: - id = str(index) - #transitionElementList = getTransitionElements(stateElement) - if isSubMachine(stateElement): - a = 1 - #createSubMachine(stateElement); - outputHeaderFile.write(' static const int STATE_%s = %d;\n' % (id, index)) - + for state in rootStateMachine.stateList: + tmpStr = ' /**\n' + tmpStr += ' * state %s\n' % (state.name) + tmpStr += ' */\n' + tmpStr += ' static const int STATE_%d = %d;\n\n' % (state.id, state.id) + outputHeaderFile.write(tmpStr) + outputHeaderFile.write('\n') + for transition in rootStateMachine.transitionList: + tmpStr = ' /**\n' + tmpStr += ' * transition %s\n' % (transition.name) + tmpStr += ' */\n' + tmpStr += ' static const int TRAN_%d = %d;\n\n' % (transition.id, transition.id) + outputHeaderFile.write(tmpStr) + outputHeaderFile.write('\n') + for event in rootStateMachine.eventList: + tmpStr = ' /**\n' + tmpStr += ' * event %s\n' % (event.name) + tmpStr += ' */\n' + tmpStr += ' static const int EVENT_%d = %d;\n\n' % (event.id, event.id) + outputHeaderFile.write(tmpStr) + outputHeaderFile.write('\n') + + header_file_end = header_file_end.replace('$className', className) outputHeaderFile.write(header_file_end) + + cpp_file_begin = cpp_file_begin.replace('$className', className) + outputCppFile.write(cpp_file_begin) + + for state in rootStateMachine.stateList: + tmpStr = ' addState(STATE_%d, "%s"); \n' % (state.id, state.name) + outputCppFile.write(tmpStr) + outputCppFile.write('\n') + + tmpStr = ' setInitState(STATE_%d); \n' % (rootStateMachine.initState.id) + outputCppFile.write(tmpStr) + outputCppFile.write('\n') + + for state in rootStateMachine.stateList: + for eventMap in state.eventMapList: + event = eventMap['event'] + transition = eventMap['transition'] + tmpStr = ' addTransition(TRAN_%d, STATE_%d, EVENT_%d, STATE_%d, "%s"); \n' % (transition.id, transition.fromState.id, transition.toState.id, event.id, state.name) + outputCppFile.write(tmpStr) + outputCppFile.write('\n') + + cpp_file_end = cpp_file_end.replace('$className', className) + outputCppFile.write(cpp_file_end) pass From 4ca534c13be79699b34353bd61773efe9c764aff Mon Sep 17 00:00:00 2001 From: xiongyee2000 Date: Sun, 22 Jul 2018 01:52:08 +0800 Subject: [PATCH 5/7] scxml2cpp.py supports subMachine. --- tools/scxml_converter/car.xml | 15 ++ tools/scxml_converter/scxml2cpp.py | 363 ++++++++++++++++++----------- 2 files changed, 246 insertions(+), 132 deletions(-) create mode 100644 tools/scxml_converter/car.xml diff --git a/tools/scxml_converter/car.xml b/tools/scxml_converter/car.xml new file mode 100644 index 0000000..43b88d1 --- /dev/null +++ b/tools/scxml_converter/car.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/scxml_converter/scxml2cpp.py b/tools/scxml_converter/scxml2cpp.py index 9c3ba32..5a598a4 100755 --- a/tools/scxml_converter/scxml2cpp.py +++ b/tools/scxml_converter/scxml2cpp.py @@ -7,6 +7,68 @@ from xml.dom.minidom import parse import xml.dom.minidom +from inspect import isroutine + + +headerFileBegin = ''' +#ifndef $HEADER_MACRO +#define $HEADER_MACRO + +#include +#include +#include "org/jinsha/imachine/StateMachine.h" + +using namespace org::jinsha::imachine; +''' + +classDefBegin = ''' + +class $className : public StateMachine { + +public: +''' + +classDefEnd = ''' + $className(int id); + virtual ~$className(); + +private: + $className() = delete; + $className(const ATMStateMachine& instance) = delete; + $className& operator = (const ATMStateMachine& instance) = delete; + + std::list subMachines; +};''' + +headerFileEnd = ''' + +#endif +''' + +cppFileBegin = ''' +#include "$className.h" +''' +classImplBegin = ''' + +$className::$className(int id) : + StateMachine(id, "$className") +{ +''' + +classImplEnd = ''' +}; + +$className::~$className() +{ + for (auto machine : subMachines) { + delete machine; + } +}; +''' + +cppFileEnd = ''' + +''' def getFirstLevelElementsByTagName(node, tagName): elementList = [] @@ -24,12 +86,72 @@ def getTransitionElements(stateElement): return [] pass +#If an element has sub state sub element +def hasSubState(element): + tmpList = getFirstLevelElementsByTagName(element, 'state') + if len(tmpList) > 0: + return True + else: + tmpList = getFirstLevelElementsByTagName(element, 'parallel') + if len(tmpList) > 0: + return True + else: + tmpList = getFirstLevelElementsByTagName(element, 'final') + if len(tmpList) > 0: + return True + + return False +pass + +#Get initial state element of a (scxml/state/parallel) element +def getInitialStateElement(element): + if not hasSubState(element): + return None + initialStateElement = None + initStateId = element.getAttribute('initial') #try to find initial as attribute + if initStateId != '': + foundInitState = True + else: #try to find initial as child element + tmpList = getFirstLevelElementsByTagName(element, 'initial') + if len(tmpList) > 0: + initStateId = tmpList[0].firstChild.data + foundInitState = True + else: #Then the initial shall be the first one + tmpNode = element.firstChild + while tmpNode != None: + if tmpNode.nodeType == tmpNode.ELEMENT_NODE and (tmpNode.tagName == 'state' or tmpNode.tagName == 'parallel'): +# initStateId = tmpNode.getAttribute('id') +# if initStateId == '': #id might not be present according the scxml specification +# initStateId = '_@_' +# tmpNode.setAttribute('id', '_@_') + initialStateElement = tmpNode; + foundInitState = True + return initialStateElement + tmpNode = tmpNode.nextSibling + + if not foundInitState: + #print '[ERROR] No initial state is found.' + return None + + tmpList = getFirstLevelElementsByTagName(element, "state") + if len(tmpList) == 0: + tmpList = getFirstLevelElementsByTagName(element, "parallel") + for item in tmpList: + if initStateId == item.getAttribute('id'): + initialStateElement = item + break + return initialStateElement +pass + class State: - def __init__(self, id, name, isInit = False, subState = None): + def __init__(self, id, name, parent, subMachine = None): self.id = id self.name = name - self.isInit = isInit - self.subState = subState + self.parent = parent + self.isInit = False + self.subMachine = subMachine + if subMachine != None: + subMachine.parent = self self.eventMapList = [] pass @@ -49,7 +171,9 @@ def __init__(self, id, name): pass class StateMachine: - def __init__(self): + def __init__(self, className): + self.parent = None + self.className = className self.stateList = [] self.transitionList = [] self.eventList = [] @@ -71,32 +195,14 @@ def build(self, element, isRoot = False): self.stateList = [] self.transitionList = [] self.eventList = [] + self.subMachineList = [] stateElementList = None firstElement = None foundInitState = False - initStateName = element.getAttribute('initial') #try to find initial as attribute - if initStateName == '': #try to find initial as child element - tmpList = getFirstLevelElementsByTagName(element, 'initial') - if len(tmpList) > 0: - initStateName = tmpList[0].firstChild.data - foundInitState = True - else: #Then the initial shall be the first one - tmpNode = element.firstChild - while tmpNode != None: - if tmpNode.nodeType == tmpNode.ELEMENT_NODE and (tmpNode.tagName == 'state' or tmpNode.tagName == 'parallel'): - initStateName = tmpNode.getAttribute('id') - if initStateName == '': #id might not be present according the scxml specification - initStateName = '_@_' - tmpNode.setAttribute('id', '_@_') - foundInitState = True - break - tmpNode = tmpNode.nextSibling - else: - foundInitState = True - - if not foundInitState: + initStateElement = getInitialStateElement(element) + if initStateElement == None: print '[ERROR] No initial state is found.' return 4 @@ -117,15 +223,17 @@ def build(self, element, isRoot = False): stateName = stateElement.getAttribute('id') if stateName == '': stateName = str(stateCount) - if isSubMachine(stateElement): - subMachine = StateMachine() + if hasSubState(stateElement): + subMachine = StateMachine(self.className + "_" + stateName) subMachine.build(stateElement) - state = State(stateCount, stateName, subMachine) + state = State(stateCount, stateName, self, subMachine) + self.subMachineList.append(subMachine) else: - state = State(stateCount, stateName) + state = State(stateCount, stateName, self) self.stateList.append(state) - if initStateName == stateName: + if stateElement == initStateElement: self.initState = state + state.isInit = True ################################################################ #Generate transitions and event maps @@ -157,64 +265,90 @@ def build(self, element, isRoot = False): else: #eventless transition event = Event(-1, eventName) sourceState.eventMapList.append({'event':event, 'transition': transition}) + + def outputClassDef(self, outputHeaderFile): + global classDefBegin + myClassDefBegin = classDefBegin.replace('$className', self.className) + outputHeaderFile.write(myClassDefBegin) + for state in self.stateList: + tmpStr = ' /**\n' + tmpStr += ' * state %s\n' % (state.name) + tmpStr += ' */\n' + tmpStr += ' static const int STATE_%d = %d;\n\n' % (state.id, state.id) + outputHeaderFile.write(tmpStr) + outputHeaderFile.write('\n') + for transition in self.transitionList: + tmpStr = ' /**\n' + tmpStr += ' * transition %s\n' % (transition.name) + tmpStr += ' */\n' + tmpStr += ' static const int TRAN_%d = %d;\n\n' % (transition.id, transition.id) + outputHeaderFile.write(tmpStr) + outputHeaderFile.write('\n') + for event in self.eventList: + tmpStr = ' /**\n' + tmpStr += ' * event %s\n' % (event.name) + tmpStr += ' */\n' + tmpStr += ' static const int EVENT_%d = %d;\n\n' % (event.id, event.id) + outputHeaderFile.write(tmpStr) + outputHeaderFile.write('\n') + + global classDefEnd + myClassDefEnd = classDefEnd.replace('$className', self.className) + outputHeaderFile.write(myClassDefEnd) + outputHeaderFile.write('\n') + + for subMachine in self.subMachineList: + subMachine.outputClassDef(outputHeaderFile) + outputHeaderFile.write('\n') + + pass + + def outputClassImpl(self, outputCppFile): + global classImplBegin + myClassImplBegin = classImplBegin.replace('$className', self.className) + outputCppFile.write(myClassImplBegin) + + for state in self.stateList: + tmpStr = ' addState(STATE_%d, "%s"); \n' % (state.id, state.name) + outputCppFile.write(tmpStr) + outputCppFile.write('\n') + + tmpStr = ' setInitState(STATE_%d); \n' % (self.initState.id) + outputCppFile.write(tmpStr) + outputCppFile.write('\n') + + for state in self.stateList: + for eventMap in state.eventMapList: + event = eventMap['event'] + transition = eventMap['transition'] + tmpStr = ' addTransition(TRAN_%d, STATE_%d, STATE_%d, EVENT_%d, "%s"); \n' % (transition.id, transition.fromState.id, transition.toState.id, event.id, transition.fromState.name + "->" + transition.toState.name) + outputCppFile.write(tmpStr) + outputCppFile.write('\n') + + for subMachine in self.subMachineList: + tmpStr = ' {\n' + tmpStr += ' StateMachine* subMachine = new %s(0, %s);\n' % (subMachine.className, subMachine.className) + tmpStr += ' setSubMachine(STATE_%d, subMachine, true); \n' % (subMachine.parent.id) + tmpStr += ' subMachines.push_back(subMachine); \n' + tmpStr += ' };\n' + outputCppFile.write(tmpStr) + + global classImplEnd + myClassImplEnd = classImplEnd.replace('$className', self.className) + outputCppFile.write(myClassImplEnd) + outputCppFile.write('\n') + + for subMachine in self.subMachineList: + subMachine.outputClassImpl(outputCppFile) + + pass pass -def buildSubState(stateElement, stateObject): - return -pass - -def main(argv): - header_file_begin = ''' -#ifndef $HEADER_MACRO -#define $HEADER_MACRO -#include -#include -#include "org/jinsha/imachine/StateMachine.h" - -using namespace org::jinsha::imachine; - -class $className : public StateMachine { - -public: -''' - - header_file_end = ''' - $className(int id); - virtual ~$className(); - -private: - $className() = delete; - $className(const ATMStateMachine& instance) = delete; - $className& operator = (const ATMStateMachine& instance) = delete; - - std::list subMachines; -}; - -#endif -''' - - cpp_file_begin = ''' -#include "$className.h" - -$className::$className(int id) : - StateMachine(id, "$className") -{ -''' - - cpp_file_end = ''' -}; - -$className::~$className() -{ - for (auto machine : subMachines) { - delete machine; - } -}; +def main(argv): -''' scxmlFilePath = None outputDir = None @@ -296,7 +430,7 @@ class $className : public StateMachine { print "Failed to create outputDir: " + outputDir return 3 - rootStateMachine = StateMachine() + rootStateMachine = StateMachine(className) rootStateMachine.build(rootElement, True) outputHeaderFileName = outputDir + '/' + className + '.h'; @@ -304,57 +438,22 @@ class $className : public StateMachine { outputHeaderFile = open(outputHeaderFileName, 'w') outputCppFile = open(outputCppFileName, 'w') headerMacro = className.upper() + '_H_' - header_file_begin = header_file_begin.replace('$HEADER_MACRO', headerMacro) - header_file_begin = header_file_begin.replace('$className', className) - outputHeaderFile.write(header_file_begin) + global headerFileBegin + headerFileBegin = headerFileBegin.replace('$HEADER_MACRO', headerMacro) + headerFileBegin = headerFileBegin.replace('$className', className) + outputHeaderFile.write(headerFileBegin) + rootStateMachine.outputClassDef(outputHeaderFile) - for state in rootStateMachine.stateList: - tmpStr = ' /**\n' - tmpStr += ' * state %s\n' % (state.name) - tmpStr += ' */\n' - tmpStr += ' static const int STATE_%d = %d;\n\n' % (state.id, state.id) - outputHeaderFile.write(tmpStr) - outputHeaderFile.write('\n') - for transition in rootStateMachine.transitionList: - tmpStr = ' /**\n' - tmpStr += ' * transition %s\n' % (transition.name) - tmpStr += ' */\n' - tmpStr += ' static const int TRAN_%d = %d;\n\n' % (transition.id, transition.id) - outputHeaderFile.write(tmpStr) - outputHeaderFile.write('\n') - for event in rootStateMachine.eventList: - tmpStr = ' /**\n' - tmpStr += ' * event %s\n' % (event.name) - tmpStr += ' */\n' - tmpStr += ' static const int EVENT_%d = %d;\n\n' % (event.id, event.id) - outputHeaderFile.write(tmpStr) - outputHeaderFile.write('\n') + global cppFileBegin + cppFileBegin = cppFileBegin.replace('$className', className) + outputCppFile.write(cppFileBegin) + rootStateMachine.outputClassImpl(outputCppFile) + global headerFileEnd + outputHeaderFile.write(headerFileEnd) + global cppFileEnd + outputHeaderFile.write(cppFileEnd) - header_file_end = header_file_end.replace('$className', className) - outputHeaderFile.write(header_file_end) - - cpp_file_begin = cpp_file_begin.replace('$className', className) - outputCppFile.write(cpp_file_begin) - - for state in rootStateMachine.stateList: - tmpStr = ' addState(STATE_%d, "%s"); \n' % (state.id, state.name) - outputCppFile.write(tmpStr) - outputCppFile.write('\n') - - tmpStr = ' setInitState(STATE_%d); \n' % (rootStateMachine.initState.id) - outputCppFile.write(tmpStr) - outputCppFile.write('\n') - - for state in rootStateMachine.stateList: - for eventMap in state.eventMapList: - event = eventMap['event'] - transition = eventMap['transition'] - tmpStr = ' addTransition(TRAN_%d, STATE_%d, EVENT_%d, STATE_%d, "%s"); \n' % (transition.id, transition.fromState.id, transition.toState.id, event.id, state.name) - outputCppFile.write(tmpStr) - outputCppFile.write('\n') - - cpp_file_end = cpp_file_end.replace('$className', className) - outputCppFile.write(cpp_file_end) + pass From 9118462a8759f39594b8e788c31c1be3c09a0d97 Mon Sep 17 00:00:00 2001 From: xiongyee2000 Date: Sun, 22 Jul 2018 21:52:01 +0800 Subject: [PATCH 6/7] FINAL state --- tools/scxml_converter/car.xml | 4 ++ tools/scxml_converter/scxml2cpp.py | 64 +++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/tools/scxml_converter/car.xml b/tools/scxml_converter/car.xml index 43b88d1..82847a6 100644 --- a/tools/scxml_converter/car.xml +++ b/tools/scxml_converter/car.xml @@ -9,7 +9,11 @@ + + + + \ No newline at end of file diff --git a/tools/scxml_converter/scxml2cpp.py b/tools/scxml_converter/scxml2cpp.py index 5a598a4..2a3a98f 100755 --- a/tools/scxml_converter/scxml2cpp.py +++ b/tools/scxml_converter/scxml2cpp.py @@ -149,6 +149,7 @@ def __init__(self, id, name, parent, subMachine = None): self.name = name self.parent = parent self.isInit = False + self.isFinal = False self.subMachine = subMachine if subMachine != None: subMachine.parent = self @@ -157,15 +158,15 @@ def __init__(self, id, name, parent, subMachine = None): pass class Transition: - def __init__(self, id, name, fromState, toState): + def __init__(self, id, fromState, toState): self.id = id - self.name = name + self.name = fromState.name + '->' + toState.name self.fromState = fromState self.toState = toState pass class Event: - def __init__(self, id, name): + def __init__(self, id, name = ''): self.id = id self.name = name pass @@ -206,16 +207,10 @@ def build(self, element, isRoot = False): print '[ERROR] No initial state is found.' return 4 - stateElementList = getFirstLevelElementsByTagName(element, 'state') -# if isRoot: -# stateElementList = [] -# stateElementList.append(element) -# else: -# stateElementList = getFirstLevelElementsByTagName(element, 'state') - ################################################################ #Generate states ################################################################ + stateElementList = getFirstLevelElementsByTagName(element, 'state') stateCount = 0 for stateElement in stateElementList: stateCount += 1 @@ -235,6 +230,19 @@ def build(self, element, isRoot = False): self.initState = state state.isInit = True + finalStateElementList = getFirstLevelElementsByTagName(element, 'final') + if len(finalStateElementList) > 1: + print '[ERROR] Multiple elements are current not supported. (Is multiple final really necessary?)' + exit(4) + for stateElement in finalStateElementList: + stateCount += 1 + stateName = stateElement.getAttribute('id') + if stateName == '': + stateName = str(stateCount) + state = State(-1, stateName, self) + state.isFinal = True + self.stateList.append(state) + ################################################################ #Generate transitions and event maps ################################################################ @@ -246,13 +254,14 @@ def build(self, element, isRoot = False): transitionElementList = getFirstLevelElementsByTagName(stateElement, 'transition') for transitionElement in transitionElementList: transitionCount += 1 + #Note only one target is allowed here targetStateName = transitionElement.getAttribute('target') if targetStateName == '': - print 'transition without a target not support.' - exit -1 + print '[ERROR] transition without a target not support.' + exit(5) sourceState = self.getStateByName(self.stateList[stateIndex].name) targetState = self.getStateByName(targetStateName) - transition = Transition(transitionCount, targetStateName, sourceState, targetState) + transition = Transition(transitionCount, sourceState, targetState) self.transitionList.append(transition) eventName = transitionElement.getAttribute('event') if eventName != '': @@ -263,20 +272,25 @@ def build(self, element, isRoot = False): self.eventList.append(event) sourceState.eventMapList.append({'event':event, 'transition': transition}) else: #eventless transition - event = Event(-1, eventName) + event = Event(-1) sourceState.eventMapList.append({'event':event, 'transition': transition}) def outputClassDef(self, outputHeaderFile): global classDefBegin myClassDefBegin = classDefBegin.replace('$className', self.className) outputHeaderFile.write(myClassDefBegin) + for state in self.stateList: tmpStr = ' /**\n' tmpStr += ' * state %s\n' % (state.name) tmpStr += ' */\n' - tmpStr += ' static const int STATE_%d = %d;\n\n' % (state.id, state.id) + if state.isFinal: + tmpStr += ' static const int STATE_FINAL = State::FINAL_STATE_ID;\n' + else: + tmpStr += ' static const int STATE_%d = %d;\n\n' % (state.id, state.id) outputHeaderFile.write(tmpStr) outputHeaderFile.write('\n') + for transition in self.transitionList: tmpStr = ' /**\n' tmpStr += ' * transition %s\n' % (transition.name) @@ -284,6 +298,7 @@ def outputClassDef(self, outputHeaderFile): tmpStr += ' static const int TRAN_%d = %d;\n\n' % (transition.id, transition.id) outputHeaderFile.write(tmpStr) outputHeaderFile.write('\n') + for event in self.eventList: tmpStr = ' /**\n' tmpStr += ' * event %s\n' % (event.name) @@ -309,7 +324,12 @@ def outputClassImpl(self, outputCppFile): outputCppFile.write(myClassImplBegin) for state in self.stateList: - tmpStr = ' addState(STATE_%d, "%s"); \n' % (state.id, state.name) + toStateIdStr = None + if state.isFinal: + stateIdStr = 'STATE_FINAL' + else: + stateIdStr = str(state.id) + tmpStr = ' addState(%s, "%s"); \n' % (stateIdStr, state.name) outputCppFile.write(tmpStr) outputCppFile.write('\n') @@ -321,13 +341,21 @@ def outputClassImpl(self, outputCppFile): for eventMap in state.eventMapList: event = eventMap['event'] transition = eventMap['transition'] - tmpStr = ' addTransition(TRAN_%d, STATE_%d, STATE_%d, EVENT_%d, "%s"); \n' % (transition.id, transition.fromState.id, transition.toState.id, event.id, transition.fromState.name + "->" + transition.toState.name) + toStateIdStr = None + if transition.toState.isFinal: + toStateIdStr = 'STATE_FINAL' + else: + toStateIdStr = 'STATE_' + str(transition.toState.id) + if event.name == '': #event less transition + tmpStr = ' addTransition(TRAN_%d, STATE_%d, %s, "%s"); \n' % (transition.id, transition.fromState.id, toStateIdStr, transition.name) + else: + tmpStr = ' addTransition(TRAN_%d, STATE_%d, %s, EVENT_%d, "%s"); \n' % (transition.id, transition.fromState.id, toStateIdStr, event.id, transition.name) outputCppFile.write(tmpStr) outputCppFile.write('\n') for subMachine in self.subMachineList: tmpStr = ' {\n' - tmpStr += ' StateMachine* subMachine = new %s(0, %s);\n' % (subMachine.className, subMachine.className) + tmpStr += ' StateMachine* subMachine = new %s(0);\n' % (subMachine.className) tmpStr += ' setSubMachine(STATE_%d, subMachine, true); \n' % (subMachine.parent.id) tmpStr += ' subMachines.push_back(subMachine); \n' tmpStr += ' };\n' From 8cfe9ba978731b7647ff7b9e770892f044d9b7c2 Mon Sep 17 00:00:00 2001 From: xiongyee2000 Date: Sun, 22 Jul 2018 22:43:44 +0800 Subject: [PATCH 7/7] Updated README.md --- README.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1ba7aaf..ee1537c 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,15 @@ Normally a state machine is predefined by users. But advanced machine intelligen * By using NSM and DAR the transition logic of a state machine is totally decoupled from user action logic of an application; and by decoupling the transition logic from the user action logic, the framework improves the flexibility and reusability of a state machine. * With the State Machine Runtime Dynamic feature, the framework constructs a fundamental basis for the advanced machine learning and machine intelligence. -# Build +# Main Features +## Dynamic State Machine +Support create/change state machine at runtime. +## Dynamic Sub State Machine +Support attach sub state machine at runtime. +## Parallel State Machine +To be supported in future. +# Build * Go to project root directory * Run ./build.sh * After build is successfully done: @@ -55,5 +62,26 @@ Normally a state machine is predefined by users. But advanced machine intelligen * Run ./build-ut.sh * After build is successfully done, the ut applicatioin will be generated at ./ut_build/bin/imachine_ut, and can run it to check the UT result. - - +# SCXML Converter +By using the scxml-to-cpp converter, the standard SCXML state machine representation can be converted to C++ classes, which can then fit into the IMachine framework. +##### Tags supported according to the SCXML standard: +###### Core Constructs: +- <scxml> +- <state> +- <transiton> (targetless transition not supported) +- <event> +- <initial> +- <final> (Only one final not supported) + +##### Tags **NOT** supported according to the SCXML standard: +###### Core Constructs: +- <parallel> (to be supported in future) +- <history> (to be supported in future) +- <onentry> (intentionally not to be supported based on design) +- <onexit> (intentionally not to be supported based on design) + +###### Executable Content (intentionally not to be supported based on design) +###### Data Model and Data Manipulation (intentionally not to be supported based on design) +###### External Communications (Not considered for now) +#### How to use +python <repo_root>/tools/scxml_converter/scxml2cpp.py -h