forked from Archive/PX4-Autopilot
202 lines
6.3 KiB
Python
Executable File
202 lines
6.3 KiB
Python
Executable File
#!/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()
|