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
diff --git a/tools/scxml_converter/car.xml b/tools/scxml_converter/car.xml
new file mode 100644
index 0000000..82847a6
--- /dev/null
+++ b/tools/scxml_converter/car.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/scxml_converter/scxml2cpp.py b/tools/scxml_converter/scxml2cpp.py
new file mode 100755
index 0000000..2a3a98f
--- /dev/null
+++ b/tools/scxml_converter/scxml2cpp.py
@@ -0,0 +1,489 @@
+#!/usr/bin/python
+#coding=utf-8
+
+import getopt
+import sys
+import os
+
+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 = []
+ 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
+
+#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, parent, subMachine = None):
+ self.id = id
+ self.name = name
+ self.parent = parent
+ self.isInit = False
+ self.isFinal = False
+ self.subMachine = subMachine
+ if subMachine != None:
+ subMachine.parent = self
+
+ self.eventMapList = []
+pass
+
+class Transition:
+ def __init__(self, id, fromState, toState):
+ self.id = id
+ self.name = fromState.name + '->' + toState.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, className):
+ self.parent = None
+ self.className = className
+ 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 = []
+ self.subMachineList = []
+
+ stateElementList = None
+ firstElement = None
+ foundInitState = False
+
+ initStateElement = getInitialStateElement(element)
+ if initStateElement == None:
+ print '[ERROR] No initial state is found.'
+ return 4
+
+ ################################################################
+ #Generate states
+ ################################################################
+ stateElementList = getFirstLevelElementsByTagName(element, 'state')
+ stateCount = 0
+ for stateElement in stateElementList:
+ stateCount += 1
+ state = None
+ stateName = stateElement.getAttribute('id')
+ if stateName == '':
+ stateName = str(stateCount)
+ if hasSubState(stateElement):
+ subMachine = StateMachine(self.className + "_" + stateName)
+ subMachine.build(stateElement)
+ state = State(stateCount, stateName, self, subMachine)
+ self.subMachineList.append(subMachine)
+ else:
+ state = State(stateCount, stateName, self)
+ self.stateList.append(state)
+ if stateElement == initStateElement:
+ 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
+ ################################################################
+ transitionCount = 0
+ eventCount = 0
+ stateIndex = -1
+ for stateElement in stateElementList:
+ stateIndex += 1
+ transitionElementList = getFirstLevelElementsByTagName(stateElement, 'transition')
+ for transitionElement in transitionElementList:
+ transitionCount += 1
+ #Note only one target is allowed here
+ targetStateName = transitionElement.getAttribute('target')
+ if targetStateName == '':
+ print '[ERROR] transition without a target not support.'
+ exit(5)
+ sourceState = self.getStateByName(self.stateList[stateIndex].name)
+ targetState = self.getStateByName(targetStateName)
+ transition = Transition(transitionCount, 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)
+ 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'
+ 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)
+ 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:
+ 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')
+
+ 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']
+ 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);\n' % (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 main(argv):
+
+
+ scxmlFilePath = None
+ outputDir = None
+ namespace = None
+ className = 'NoNameStateMachine'
+
+ 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.
+-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
+ 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: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
+ return -2
+
+ if (not os.path.exists(scxmlFilePath)):
+ print "scxmlFile does not exist."
+ return 1
+
+ ################################################################
+ #Parse input scxml file
+ ################################################################
+ domTree = xml.dom.minidom.parse(scxmlFilePath)
+ rootElement = domTree.documentElement
+ initStateName = None
+
+ #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
+
+ if (not os.path.exists(outputDir)):
+ os.mkdir(outputDir, 0755)
+ if (not os.path.exists(outputDir)):
+ print "Failed to create outputDir: " + outputDir
+ return 3
+
+ rootStateMachine = StateMachine(className)
+ 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 headerFileBegin
+ headerFileBegin = headerFileBegin.replace('$HEADER_MACRO', headerMacro)
+ headerFileBegin = headerFileBegin.replace('$className', className)
+ outputHeaderFile.write(headerFileBegin)
+ rootStateMachine.outputClassDef(outputHeaderFile)
+
+ global cppFileBegin
+ cppFileBegin = cppFileBegin.replace('$className', className)
+ outputCppFile.write(cppFileBegin)
+ rootStateMachine.outputClassImpl(outputCppFile)
+ global headerFileEnd
+ outputHeaderFile.write(headerFileEnd)
+ global cppFileEnd
+ outputHeaderFile.write(cppFileEnd)
+
+
+
+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
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
)