diff options
Diffstat (limited to 'doc/scripts/gen_state_diagram.py')
-rwxr-xr-x | doc/scripts/gen_state_diagram.py | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/doc/scripts/gen_state_diagram.py b/doc/scripts/gen_state_diagram.py new file mode 100755 index 00000000..a7399bb2 --- /dev/null +++ b/doc/scripts/gen_state_diagram.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +from __future__ import print_function + +import re +import sys + + +def do_filter(generator): + return acc_lines(remove_multiline_comments(to_char(remove_single_line_comments(generator)))) + + +def acc_lines(generator): + current = "" + for i in generator: + current += i + if i == ';' or \ + i == '{' or \ + i == '}': + yield current.lstrip("\n") + current = "" + + +def to_char(generator): + for line in generator: + for char in line: + if char is not '\n': + yield char + else: + yield ' ' + + +def remove_single_line_comments(generator): + for i in generator: + if len(i) and i[0] == '#': + continue + yield re.sub(r'//.*', '', i) + + +def remove_multiline_comments(generator): + saw = "" + in_comment = False + for char in generator: + if in_comment: + if saw is "*": + if char is "/": + in_comment = False + saw = "" + if char is "*": + saw = "*" + continue + if saw is "/": + if char is '*': + in_comment = True + saw = "" + continue + else: + yield saw + saw = "" + if char is '/': + saw = "/" + continue + yield char + + +class StateMachineRenderer(object): + def __init__(self): + self.states = {} # state -> parent + self.machines = {} # state-> initial + self.edges = {} # event -> [(state, state)] + + self.context = [] # [(context, depth_encountered)] + self.context_depth = 0 + self.state_contents = {} + self.subgraphnum = 0 + self.clusterlabel = {} + + def __str__(self): + return "-------------------\n\nstates: %s\n\n machines: %s\n\n edges: %s\n\n context %s\n\n state_contents %s\n\n--------------------" % ( + self.states, + self.machines, + self.edges, + self.context, + self.state_contents + ) + + def read_input(self, input_lines): + previous_line = None + for line in input_lines: + self.get_state(line) + self.get_event(line) + # pass two lines at a time to get the context so that regexes can + # match on split signatures + self.get_context(line, previous_line) + previous_line = line + + def get_context(self, line, previous_line): + match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(const (?P<event>\w+)", line) + if match is None and previous_line is not None: + # it is possible that we need to match on the previous line as well, so join + # them to make them one line and try and get this matching + joined_line = ' '.join([previous_line, line]) + match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(\s*const (?P<event>\w+)", joined_line) + if match is not None: + self.context.append((match.group('tag'), self.context_depth, match.group('event'))) + if '{' in line: + self.context_depth += 1 + if '}' in line: + self.context_depth -= 1 + while len(self.context) and self.context[-1][1] == self.context_depth: + self.context.pop() + + def get_state(self, line): + if "boost::statechart::state_machine" in line: + tokens = re.search( + r"boost::statechart::state_machine<\s*(\w*),\s*(\w*)\s*>", + line) + if tokens is None: + raise Exception("Error: malformed state_machine line: " + line) + self.machines[tokens.group(1)] = tokens.group(2) + self.context.append((tokens.group(1), self.context_depth, "")) + return + if "boost::statechart::state" in line: + tokens = re.search( + r"boost::statechart::state<\s*(\w*),\s*(\w*)\s*,?\s*(\w*)\s*>", + line) + if tokens is None: + raise Exception("Error: malformed state line: " + line) + self.states[tokens.group(1)] = tokens.group(2) + if tokens.group(2) not in self.state_contents.keys(): + self.state_contents[tokens.group(2)] = [] + self.state_contents[tokens.group(2)].append(tokens.group(1)) + if tokens.group(3) is not "": + self.machines[tokens.group(1)] = tokens.group(3) + self.context.append((tokens.group(1), self.context_depth, "")) + return + + def get_event(self, line): + if "boost::statechart::transition" in line: + for i in re.finditer(r'boost::statechart::transition<\s*([\w:]*)\s*,\s*(\w*)\s*>', + line): + if i.group(1) not in self.edges.keys(): + self.edges[i.group(1)] = [] + if len(self.context) is 0: + raise Exception("no context at line: " + line) + self.edges[i.group(1)].append((self.context[-1][0], i.group(2))) + i = re.search("return\s+transit<\s*(\w*)\s*>()", line) + if i is not None: + if len(self.context) is 0: + raise Exception("no context at line: " + line) + if self.context[-1][2] is "": + raise Exception("no event in context at line: " + line) + if self.context[-1][2] not in self.edges.keys(): + self.edges[self.context[-1][2]] = [] + self.edges[self.context[-1][2]].append((self.context[-1][0], i.group(1))) + + def emit_dot(self): + top_level = [] + for state in self.machines.keys(): + if state not in self.states.keys(): + top_level.append(state) + print('Top Level States: ', top_level, file=sys.stderr) + print('digraph G {') + print('\tsize="7,7"') + print('\tcompound=true;') + for i in self.emit_state(top_level[0]): + print('\t' + i) + for i in self.edges.keys(): + for j in self.emit_event(i): + print(j) + print('}') + + def emit_state(self, state): + if state in self.state_contents.keys(): + self.clusterlabel[state] = "cluster%s" % (str(self.subgraphnum),) + yield "subgraph cluster%s {" % (str(self.subgraphnum),) + self.subgraphnum += 1 + yield """\tlabel = "%s";""" % (state,) + yield """\tcolor = "blue";""" + for j in self.state_contents[state]: + for i in self.emit_state(j): + yield "\t"+i + yield "}" + else: + found = False + for (k, v) in self.machines.items(): + if v == state: + yield state+"[shape=Mdiamond];" + found = True + break + if not found: + yield state+";" + + def emit_event(self, event): + def append(app): + retval = "[" + for i in app: + retval += (i + ",") + retval += "]" + return retval + for (fro, to) in self.edges[event]: + appendix = ['label="%s"' % (event,)] + if fro in self.machines.keys(): + appendix.append("ltail=%s" % (self.clusterlabel[fro],)) + while fro in self.machines.keys(): + fro = self.machines[fro] + if to in self.machines.keys(): + appendix.append("lhead=%s" % (self.clusterlabel[to],)) + while to in self.machines.keys(): + to = self.machines[to] + yield("%s -> %s %s;" % (fro, to, append(appendix))) + + +INPUT_GENERATOR = do_filter(line for line in sys.stdin) +RENDERER = StateMachineRenderer() +RENDERER.read_input(INPUT_GENERATOR) +RENDERER.emit_dot() |