aboutsummaryrefslogtreecommitdiff
path: root/Tools/fsm_visualisation.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/fsm_visualisation.py')
-rwxr-xr-xTools/fsm_visualisation.py201
1 files changed, 201 insertions, 0 deletions
diff --git a/Tools/fsm_visualisation.py b/Tools/fsm_visualisation.py
new file mode 100755
index 000000000..c678ef0f4
--- /dev/null
+++ b/Tools/fsm_visualisation.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python3
+
+"""fsm_visualisation.py: Create dot code and dokuwiki table from a state transition table
+
+convert dot code to png using graphviz:
+
+dot fsm.dot -Tpng -o fsm.png
+"""
+
+import argparse
+import re
+
+__author__ = "Julian Oes"
+
+def get_dot_header():
+
+ return """digraph finite_state_machine {
+ graph [ dpi = 300 ];
+ ratio = 1.5
+ node [shape = circle];"""
+
+def get_dot_footer():
+
+ return """}\n"""
+
+def main():
+
+ # parse input arguments
+ parser = argparse.ArgumentParser(description='Create dot code and dokuwiki table from a state transition table.')
+ parser.add_argument("-i", "--input-file", default=None, help="choose file to parse")
+ parser.add_argument("-d", "--dot-file", default=None, help="choose file for output dot file")
+ parser.add_argument("-t", "--table-file", default=None, help="choose file for output of table")
+ args = parser.parse_args()
+
+ # open source file
+ if args.input_file == None:
+ exit('please specify file')
+ f = open(args.input_file,'r')
+ source = f.read()
+
+ # search for state transition table and extract the table itself
+ # first look for StateTable::Tran
+ # then accept anything including newline until {
+ # but don't accept the definition (without ;)
+ # then extract anything inside the brackets until };
+ match = re.search(r'StateTable::Tran(?:.|\n!;)*\{((?:.|\n)*?)\};', source)
+
+ if not match:
+ exit('no state transition table found')
+
+ table_source = match.group(1)
+
+ # bookkeeping for error checking
+ num_errors_found = 0
+
+ states = []
+ events = []
+
+ # first get all states and events
+ for table_line in table_source.split('\n'):
+
+ match = re.search(r'/\*\s+\w+_STATE_(\w+)\s+\*/', table_line)
+ if match:
+ states.append(str(match.group(1)))
+ # go to next line
+ continue
+
+ if len(states) == 1:
+ match = re.search(r'/\*\s+EVENT_(\w+)\s+\*/', table_line)
+ if match:
+ events.append(str(match.group(1)))
+
+ print('Found %d states and %d events' % (len(states), len(events)))
+
+
+ # keep track of origin state
+ state = None
+
+ # fill dot code in here
+ dot_code = ''
+
+ # create table len(states)xlen(events)
+ transition_table = [[[] for x in range(len(states))] for y in range(len(events))]
+
+ # now fill the transition table and write the dot code
+ for table_line in table_source.split('\n'):
+
+ # get states
+ # from: /* NAV_STATE_NONE */
+ # extract only "NONE"
+ match = re.search(r'/\*\s+\w+_STATE_(\w+)\s+\*/', table_line)
+ if match:
+ state = match.group(1)
+ state_index = states.index(state)
+ # go to next line
+ continue
+
+ # can't advance without proper state
+ if state == None:
+ continue
+
+ # get event and next state
+ # from /* EVENT_READY_REQUESTED */ {ACTION(&Navigator::start_ready), NAV_STATE_READY}
+ # extract "READY_REQUESTED" and "READY" if there is ACTION
+ match_action = re.search(r'/\*\s+EVENT_(\w+)\s+\*/\s+\{ACTION\((?:.|\n)*\w+_STATE_(\w+)', table_line)
+
+ # get event and next state
+ # from /* EVENT_NONE_REQUESTED */ {NO_ACTION, NAV_STATE_NONE},
+ # extract "NONE_REQUESTED" and "NAV_STATE_NONE" if there is NO_ACTION
+ match_no_action = re.search(r'/\*\s+EVENT_(\w+)\s+\*/\s+\{NO_ACTION(?:.|\n)*\w+_STATE_(\w+)', table_line)
+
+ # ignore lines with brackets only
+ if match_action or match_no_action:
+
+ # only write arrows for actions
+ if match_action:
+ event = match_action.group(1)
+ new_state = match_action.group(2)
+ dot_code += ' ' + state + ' -> ' + new_state + '[ label = "' + event + '"];\n'
+
+ elif match_no_action:
+ event = match_no_action.group(1)
+ new_state = match_no_action.group(2)
+
+ # check for state changes without action
+ if state != new_state:
+ print('Error: no action but state change:')
+ print('State: ' + state + ' changed to: ' + new_state)
+ print(table_line)
+ num_errors_found += 1
+
+ # check for wrong events
+ if event not in events:
+ print('Error: unknown event: ' + event)
+ print(table_line)
+ num_errors_found += 1
+
+ # check for wrong new states
+ if new_state not in states:
+ print('Error: unknown new state: ' + new_state)
+ print(table_line)
+ num_errors_found += 1
+
+ # save new state in transition table
+ event_index = events.index(event)
+
+ # bold for action
+ if match_action:
+ transition_table[event_index][state_index] = '**' + new_state + '**'
+ else:
+ transition_table[event_index][state_index] = new_state
+
+
+
+ # assemble dot code
+ dot_code = get_dot_header() + dot_code + get_dot_footer()
+
+ # write or print dot file
+ if args.dot_file:
+ f = open(args.dot_file,'w')
+ f.write(dot_code)
+ print('Wrote dot file')
+ else:
+ print('##########Dot-start##########')
+ print(dot_code)
+ print('##########Dot-end############')
+
+
+ # assemble doku wiki table
+ table_code = '| ^ '
+ # start with header of all states
+ for state in states:
+ table_code += state + ' ^ '
+
+ table_code += '\n'
+
+ # add events and new states
+ for event, row in zip(events, transition_table):
+ table_code += '^ ' + event + ' | '
+ for new_state in row:
+ table_code += new_state + ' | '
+ table_code += '\n'
+
+ # write or print wiki table
+ if args.table_file:
+ f = open(args.table_file,'w')
+ f.write(table_code)
+ print('Wrote table file')
+ else:
+ print('##########Table-start########')
+ print(table_code)
+ print('##########Table-end##########')
+
+ # report obvous errors
+ if num_errors_found:
+ print('Obvious errors found: %d' % num_errors_found)
+ else:
+ print('No obvious errors found')
+
+if __name__ == '__main__':
+ main()