]>
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): | |
85 | for line in input_lines: | |
86 | self.get_state(line) | |
87 | self.get_event(line) | |
88 | self.get_context(line) | |
89 | ||
90 | def get_context(self, line): | |
91 | match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(const (?P<event>\w+)", | |
92 | line) | |
93 | if match is not None: | |
94 | self.context.append((match.group('tag'), self.context_depth, match.group('event'))) | |
95 | if '{' in line: | |
96 | self.context_depth += 1 | |
97 | if '}' in line: | |
98 | self.context_depth -= 1 | |
99 | while len(self.context) and self.context[-1][1] == self.context_depth: | |
100 | self.context.pop() | |
101 | ||
102 | def get_state(self, line): | |
103 | if "boost::statechart::state_machine" in line: | |
104 | tokens = re.search( | |
105 | r"boost::statechart::state_machine<\s*(\w*),\s*(\w*)\s*>", | |
106 | line) | |
107 | if tokens is None: | |
108 | raise "Error: malformed state_machine line: " + line | |
109 | self.machines[tokens.group(1)] = tokens.group(2) | |
110 | self.context.append((tokens.group(1), self.context_depth, "")) | |
111 | return | |
112 | if "boost::statechart::state" in line: | |
113 | tokens = re.search( | |
114 | r"boost::statechart::state<\s*(\w*),\s*(\w*)\s*,?\s*(\w*)\s*>", | |
115 | line) | |
116 | if tokens is None: | |
117 | raise "Error: malformed state line: " + line | |
118 | self.states[tokens.group(1)] = tokens.group(2) | |
119 | if tokens.group(2) not in self.state_contents.keys(): | |
120 | self.state_contents[tokens.group(2)] = [] | |
121 | self.state_contents[tokens.group(2)].append(tokens.group(1)) | |
122 | if tokens.group(3) is not "": | |
123 | self.machines[tokens.group(1)] = tokens.group(3) | |
124 | self.context.append((tokens.group(1), self.context_depth, "")) | |
125 | return | |
126 | ||
127 | def get_event(self, line): | |
128 | if "boost::statechart::transition" in line: | |
129 | for i in re.finditer(r'boost::statechart::transition<\s*([\w:]*)\s*,\s*(\w*)\s*>', | |
130 | line): | |
131 | if i.group(1) not in self.edges.keys(): | |
132 | self.edges[i.group(1)] = [] | |
133 | if len(self.context) is 0: | |
134 | raise "no context at line: " + line | |
135 | self.edges[i.group(1)].append((self.context[-1][0], i.group(2))) | |
136 | i = re.search("return\s+transit<\s*(\w*)\s*>()", line) | |
137 | if i is not None: | |
138 | if len(self.context) is 0: | |
139 | raise "no context at line: " + line | |
140 | if self.context[-1][2] is "": | |
141 | raise "no event in context at line: " + line | |
142 | if self.context[-1][2] not in self.edges.keys(): | |
143 | self.edges[self.context[-1][2]] = [] | |
144 | self.edges[self.context[-1][2]].append((self.context[-1][0], i.group(1))) | |
145 | ||
146 | def emit_dot(self): | |
147 | top_level = [] | |
148 | for state in self.machines.keys(): | |
149 | if state not in self.states.keys(): | |
150 | top_level.append(state) | |
151 | print >> sys.stderr, "Top Level States: ", str(top_level) | |
152 | print """digraph G {""" | |
153 | print '\tsize="7,7"' | |
154 | print """\tcompound=true;""" | |
155 | for i in self.emit_state(top_level[0]): | |
156 | print '\t' + i | |
157 | for i in self.edges.keys(): | |
158 | for j in self.emit_event(i): | |
159 | print j | |
160 | print """}""" | |
161 | ||
162 | def emit_state(self, state): | |
163 | if state in self.state_contents.keys(): | |
164 | self.clusterlabel[state] = "cluster%s" % (str(self.subgraphnum),) | |
165 | yield "subgraph cluster%s {" % (str(self.subgraphnum),) | |
166 | self.subgraphnum += 1 | |
167 | yield """\tlabel = "%s";""" % (state,) | |
168 | yield """\tcolor = "blue";""" | |
169 | for j in self.state_contents[state]: | |
170 | for i in self.emit_state(j): | |
171 | yield "\t"+i | |
172 | yield "}" | |
173 | else: | |
174 | found = False | |
175 | for (k, v) in self.machines.items(): | |
176 | if v == state: | |
177 | yield state+"[shape=Mdiamond];" | |
178 | found = True | |
179 | break | |
180 | if not found: | |
181 | yield state+";" | |
182 | ||
183 | def emit_event(self, event): | |
184 | def append(app): | |
185 | retval = "[" | |
186 | for i in app: | |
187 | retval += (i + ",") | |
188 | retval += "]" | |
189 | return retval | |
190 | for (fro, to) in self.edges[event]: | |
191 | appendix = ['label="%s"' % (event,)] | |
192 | if fro in self.machines.keys(): | |
193 | appendix.append("ltail=%s" % (self.clusterlabel[fro],)) | |
194 | while fro in self.machines.keys(): | |
195 | fro = self.machines[fro] | |
196 | if to in self.machines.keys(): | |
197 | appendix.append("lhead=%s" % (self.clusterlabel[to],)) | |
198 | while to in self.machines.keys(): | |
199 | to = self.machines[to] | |
200 | yield("%s -> %s %s;" % (fro, to, append(appendix))) | |
201 | ||
202 | ||
203 | INPUT_GENERATOR = do_filter(sys.stdin.xreadlines()) | |
204 | RENDERER = StateMachineRenderer() | |
205 | RENDERER.read_input(INPUT_GENERATOR) | |
206 | RENDERER.emit_dot() |