]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | #!/usr/bin/env python |
2 | import re | |
3 | import sys | |
4 | ||
5 | ||
6 | def do_filter(generator): | |
7 | return acc_lines(remove_multiline_comments(to_char(remove_single_line_comments(generator)))) | |
8 | ||
9 | ||
10 | def acc_lines(generator): | |
11 | current = "" | |
12 | for i in generator: | |
13 | current += i | |
14 | if i == ';' or \ | |
15 | i == '{' or \ | |
16 | i == '}': | |
17 | yield current.lstrip("\n") | |
18 | current = "" | |
19 | ||
20 | ||
21 | def to_char(generator): | |
22 | for line in generator: | |
23 | for char in line: | |
24 | if char is not '\n': | |
25 | yield char | |
26 | else: | |
27 | yield ' ' | |
28 | ||
29 | ||
30 | def remove_single_line_comments(generator): | |
31 | for i in generator: | |
32 | if len(i) and i[0] == '#': | |
33 | continue | |
34 | yield re.sub(r'//.*', '', i) | |
35 | ||
36 | ||
37 | def remove_multiline_comments(generator): | |
38 | saw = "" | |
39 | in_comment = False | |
40 | for char in generator: | |
41 | if in_comment: | |
42 | if saw is "*": | |
43 | if char is "/": | |
44 | in_comment = False | |
45 | saw = "" | |
46 | if char is "*": | |
47 | saw = "*" | |
48 | continue | |
49 | if saw is "/": | |
50 | if char is '*': | |
51 | in_comment = True | |
52 | saw = "" | |
53 | continue | |
54 | else: | |
55 | yield saw | |
56 | saw = "" | |
57 | if char is '/': | |
58 | saw = "/" | |
59 | continue | |
60 | yield char | |
61 | ||
62 | ||
63 | class StateMachineRenderer(object): | |
64 | def __init__(self): | |
65 | self.states = {} # state -> parent | |
66 | self.machines = {} # state-> initial | |
67 | self.edges = {} # event -> [(state, state)] | |
68 | ||
69 | self.context = [] # [(context, depth_encountered)] | |
70 | self.context_depth = 0 | |
71 | self.state_contents = {} | |
72 | self.subgraphnum = 0 | |
73 | self.clusterlabel = {} | |
74 | ||
75 | def __str__(self): | |
76 | return "-------------------\n\nstates: %s\n\n machines: %s\n\n edges: %s\n\n context %s\n\n state_contents %s\n\n--------------------" % ( | |
77 | self.states, | |
78 | self.machines, | |
79 | self.edges, | |
80 | self.context, | |
81 | self.state_contents | |
82 | ) | |
83 | ||
84 | def read_input(self, input_lines): | |
3efd9988 | 85 | previous_line = None |
7c673cae FG |
86 | for line in input_lines: |
87 | self.get_state(line) | |
88 | self.get_event(line) | |
3efd9988 FG |
89 | # pass two lines at a time to get the context so that regexes can |
90 | # match on split signatures | |
91 | self.get_context(line, previous_line) | |
92 | previous_line = line | |
93 | ||
94 | def get_context(self, line, previous_line): | |
95 | match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(const (?P<event>\w+)", line) | |
96 | if match is None and previous_line is not None: | |
97 | # it is possible that we need to match on the previous line as well, so join | |
98 | # them to make them one line and try and get this matching | |
99 | joined_line = ' '.join([previous_line, line]) | |
100 | match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(\s*const (?P<event>\w+)", joined_line) | |
7c673cae FG |
101 | if match is not None: |
102 | self.context.append((match.group('tag'), self.context_depth, match.group('event'))) | |
103 | if '{' in line: | |
104 | self.context_depth += 1 | |
105 | if '}' in line: | |
106 | self.context_depth -= 1 | |
107 | while len(self.context) and self.context[-1][1] == self.context_depth: | |
108 | self.context.pop() | |
109 | ||
110 | def get_state(self, line): | |
111 | if "boost::statechart::state_machine" in line: | |
112 | tokens = re.search( | |
113 | r"boost::statechart::state_machine<\s*(\w*),\s*(\w*)\s*>", | |
114 | line) | |
115 | if tokens is None: | |
3efd9988 | 116 | raise Exception("Error: malformed state_machine line: " + line) |
7c673cae FG |
117 | self.machines[tokens.group(1)] = tokens.group(2) |
118 | self.context.append((tokens.group(1), self.context_depth, "")) | |
119 | return | |
120 | if "boost::statechart::state" in line: | |
121 | tokens = re.search( | |
122 | r"boost::statechart::state<\s*(\w*),\s*(\w*)\s*,?\s*(\w*)\s*>", | |
123 | line) | |
124 | if tokens is None: | |
3efd9988 | 125 | raise Exception("Error: malformed state line: " + line) |
7c673cae FG |
126 | self.states[tokens.group(1)] = tokens.group(2) |
127 | if tokens.group(2) not in self.state_contents.keys(): | |
128 | self.state_contents[tokens.group(2)] = [] | |
129 | self.state_contents[tokens.group(2)].append(tokens.group(1)) | |
130 | if tokens.group(3) is not "": | |
131 | self.machines[tokens.group(1)] = tokens.group(3) | |
132 | self.context.append((tokens.group(1), self.context_depth, "")) | |
133 | return | |
134 | ||
135 | def get_event(self, line): | |
136 | if "boost::statechart::transition" in line: | |
137 | for i in re.finditer(r'boost::statechart::transition<\s*([\w:]*)\s*,\s*(\w*)\s*>', | |
138 | line): | |
139 | if i.group(1) not in self.edges.keys(): | |
140 | self.edges[i.group(1)] = [] | |
141 | if len(self.context) is 0: | |
3efd9988 | 142 | raise Exception("no context at line: " + line) |
7c673cae FG |
143 | self.edges[i.group(1)].append((self.context[-1][0], i.group(2))) |
144 | i = re.search("return\s+transit<\s*(\w*)\s*>()", line) | |
145 | if i is not None: | |
146 | if len(self.context) is 0: | |
3efd9988 | 147 | raise Exception("no context at line: " + line) |
7c673cae | 148 | if self.context[-1][2] is "": |
3efd9988 | 149 | raise Exception("no event in context at line: " + line) |
7c673cae FG |
150 | if self.context[-1][2] not in self.edges.keys(): |
151 | self.edges[self.context[-1][2]] = [] | |
152 | self.edges[self.context[-1][2]].append((self.context[-1][0], i.group(1))) | |
153 | ||
154 | def emit_dot(self): | |
155 | top_level = [] | |
156 | for state in self.machines.keys(): | |
157 | if state not in self.states.keys(): | |
158 | top_level.append(state) | |
159 | print >> sys.stderr, "Top Level States: ", str(top_level) | |
160 | print """digraph G {""" | |
161 | print '\tsize="7,7"' | |
162 | print """\tcompound=true;""" | |
163 | for i in self.emit_state(top_level[0]): | |
164 | print '\t' + i | |
165 | for i in self.edges.keys(): | |
166 | for j in self.emit_event(i): | |
167 | print j | |
168 | print """}""" | |
169 | ||
170 | def emit_state(self, state): | |
171 | if state in self.state_contents.keys(): | |
172 | self.clusterlabel[state] = "cluster%s" % (str(self.subgraphnum),) | |
173 | yield "subgraph cluster%s {" % (str(self.subgraphnum),) | |
174 | self.subgraphnum += 1 | |
175 | yield """\tlabel = "%s";""" % (state,) | |
176 | yield """\tcolor = "blue";""" | |
177 | for j in self.state_contents[state]: | |
178 | for i in self.emit_state(j): | |
179 | yield "\t"+i | |
180 | yield "}" | |
181 | else: | |
182 | found = False | |
183 | for (k, v) in self.machines.items(): | |
184 | if v == state: | |
185 | yield state+"[shape=Mdiamond];" | |
186 | found = True | |
187 | break | |
188 | if not found: | |
189 | yield state+";" | |
190 | ||
191 | def emit_event(self, event): | |
192 | def append(app): | |
193 | retval = "[" | |
194 | for i in app: | |
195 | retval += (i + ",") | |
196 | retval += "]" | |
197 | return retval | |
198 | for (fro, to) in self.edges[event]: | |
199 | appendix = ['label="%s"' % (event,)] | |
200 | if fro in self.machines.keys(): | |
201 | appendix.append("ltail=%s" % (self.clusterlabel[fro],)) | |
202 | while fro in self.machines.keys(): | |
203 | fro = self.machines[fro] | |
204 | if to in self.machines.keys(): | |
205 | appendix.append("lhead=%s" % (self.clusterlabel[to],)) | |
206 | while to in self.machines.keys(): | |
207 | to = self.machines[to] | |
208 | yield("%s -> %s %s;" % (fro, to, append(appendix))) | |
209 | ||
210 | ||
211 | INPUT_GENERATOR = do_filter(sys.stdin.xreadlines()) | |
212 | RENDERER = StateMachineRenderer() | |
213 | RENDERER.read_input(INPUT_GENERATOR) | |
214 | RENDERER.emit_dot() |