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