blob: 85e2806f147b67adb102394df63c173901b258e9 [file] [log] [blame]
Neels Hofmeyr0898a002016-11-16 14:36:29 +01001#!/usr/bin/env python
2
3__doc__ = '''
4fsm-to-dot: convert FSM definitons to graph images
5
6Usage:
7 ./fsm-to-dot.py ~/openbsc/openbsc/src/libvlr/*.c
Neels Hofmeyra568af22017-10-23 04:03:19 +02008 for f in *.dot ; do dot -Tpng "$f" > "$f.png"; done
Neels Hofmeyr0898a002016-11-16 14:36:29 +01009 # dot comes from 'apt-get install graphviz'
10
11Looks for osmo_fsm finite state machine definitions and madly parses .c files
12to draw graphs of them. This uses wild regexes that rely on coding style etc..
13No proper C parsing is done here (pycparser sucked, unfortunately).
14'''
15
Neels Hofmeyra568af22017-10-23 04:03:19 +020016import sys, re, os
Neels Hofmeyr0898a002016-11-16 14:36:29 +010017
Neels Hofmeyr167f8082018-03-25 01:01:32 +010018if '-h' in sys.argv or '--help' in sys.argv:
19 print(__doc__)
20 exit(0)
21
Neels Hofmeyr0898a002016-11-16 14:36:29 +010022def err(msg):
23 sys.stderr.write(msg + '\n')
24
25class listdict(object):
26 def __getattr__(ld, name):
27 if name == 'add':
28 return ld.__getattribute__(name)
29 return ld.__dict__.__getattribute__(name)
30
31 def _have(ld, name):
32 l = ld.__dict__.get(name)
33 if not l:
34 l = []
35 ld.__dict__[name] = l
36 return l
37
38 def add(ld, name, item):
39 l = ld._have(name)
40 l.append(item)
41 return ld
42
43 def add_dict(ld, d):
44 for k,v in d.items():
45 ld.add(k, v)
46
47 def __setitem__(ld, name, val):
48 return ld.__dict__.__setitem__(name, val)
49
50 def __getitem__(ld, name):
51 return ld.__dict__.__getitem__(name)
52
53 def __str__(ld):
54 return ld.__dict__.__str__()
55
56 def __repr__(ld):
57 return ld.__dict__.__repr__()
58
59 def update(ld, other_ld):
60 for name, items in other_ld.items():
61 ld.extend(name, items)
62 return ld
63
64 def extend(ld, name, vals):
65 l = ld._have(name)
66 l.extend(vals)
67 return ld
68
69re_state_start = re.compile(r'\[([A-Z_][A-Z_0-9]*)\]')
Neels Hofmeyra568af22017-10-23 04:03:19 +020070re_event_alternatives = [
71 re.compile(r'\(1 *<< *([A-Z_][A-Z_0-9]*)\)'),
72 re.compile(r'S\(([A-Z_][A-Z_0-9]*)\)'),
73 ]
Neels Hofmeyr0898a002016-11-16 14:36:29 +010074re_action = re.compile(r'.action *= *([a-z_][a-z_0-9]*)')
75
Neels Hofmeyra568af22017-10-23 04:03:19 +020076re_insane_dot_name_chars = re.compile('[^a-zA-Z_]')
77
Neels Hofmeyr0898a002016-11-16 14:36:29 +010078def state_starts(line):
79 m = re_state_start.search(line)
80 if m:
81 return m.group(1)
82 return None
83
84def in_event_starts(line):
85 return line.find('in_event_mask') >= 0
86
87def out_state_starts(line):
88 return line.find('out_state_mask') >= 0
89
90def states_or_events(line):
Neels Hofmeyra568af22017-10-23 04:03:19 +020091 results = []
92 for one_re in re_event_alternatives:
93 results.extend(one_re.findall(line))
94 return results
Neels Hofmeyr0898a002016-11-16 14:36:29 +010095
96def parse_action(line):
97 a = re_action.findall(line)
98 if a:
99 return a[0]
100 return None
101
102def _common_prefix(a, b):
103 for l in reversed(range(1,len(a))):
104 aa = a[:l+1]
105 if b.startswith(aa):
106 return aa
107 return ''
108
109def common_prefix(strs):
110 if not strs:
111 return ''
112 p = None
113 for s in strs:
114 if p is None:
115 p = s
116 continue
117 p = _common_prefix(p, s)
118 if not p:
119 return ''
120 return p
121
122KIND_STATE = 'KIND_STATE'
123KIND_FUNC = 'KIND_FUNC'
124KIND_FSM = 'KIND_FSM'
125BOX_SHAPES = {
126 KIND_STATE : None,
127 KIND_FUNC : 'box',
128 KIND_FSM : 'box3d',
129}
130
131class Event:
132 def __init__(event, name):
133 event.name = name
134 event.short_name = name
135
136 def __cmp__(event, other):
137 return cmp(event.name, other.name)
138
139class Edge:
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200140 def __init__(edge, to_state, event_name=None, style=None, action=None, color=None, arrow_head=None):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100141 edge.to_state = to_state
142 edge.style = style
Neels Hofmeyrfcf79922018-03-25 01:03:26 +0100143 edge.color = color
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200144 edge.arrow_head = arrow_head
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100145 edge.events = []
146 edge.actions = []
147 edge.add_event_name(event_name)
148 edge.add_action(action)
149
150 def add_event_name(edge, event_name):
151 if not event_name:
152 return
153 edge.add_event(Event(event_name))
154
155 def add_event(edge, event):
156 if not event:
157 return
158 if event in edge.events:
159 return
160 edge.events.append(event)
161
162 def add_events(edge, events):
163 for event in events:
164 edge.add_event(event)
165
166 def add_action(edge, action):
167 if not action or action in edge.actions:
168 return
169 edge.actions.append(action)
170
171 def add_actions(edge, actions):
172 for action in actions:
173 edge.add_action(action)
174
175 def event_names(edge):
176 return sorted([event.name for event in edge.events])
177
178 def event_labels(edge):
179 return sorted([event.short_name for event in edge.events])
180
181 def action_labels(edge):
182 return sorted([action + '()' for action in edge.actions])
183
184 def has_event_name(edge, event_name):
185 return event_name in edge.event_names()
186
187class State:
188 name = None
189 short_name = None
190 action = None
191 label = None
192 in_event_names = None
193 out_state_names = None
194 out_edges = None
195 kind = None
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200196 color = None
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100197
198 def __init__(state):
199 state.in_event_names = []
200 state.out_state_names = []
201 state.out_edges = []
202 state.kind = KIND_STATE
203
204 def add_out_edge(state, edge):
205 for out_edge in state.out_edges:
206 if out_edge.to_state is edge.to_state:
207 if out_edge.style == edge.style:
208 out_edge.add_events(edge.events)
209 out_edge.add_actions(edge.actions)
210 return
Neels Hofmeyr46145e82018-03-25 01:02:35 +0100211 elif out_edge.to_state.get_label() == edge.to_state.get_label():
212 # sanity: there already is an edge to a state that a) is not identical to the target state of the
213 # newly added edge but b) has the same label.
214 raise Exception('Two distinct states exist with identical label: %r: states %r and %r.'
215 % (out_edge.to_state.get_label(), out_edge.to_state, edge.to_state))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100216 state.out_edges.append(edge)
217
218 def get_label(state):
219 if state.label:
220 return state.label
221 l = [state.short_name]
222 if state.action:
223 if state.short_name == state.action:
224 l = []
225 l.append(state.action + '()')
226 return r'\n'.join(l)
227
228 def event_names(state):
229 event_names = []
230 for out_edge in state.out_edges:
231 event_names.extend(out_edge.event_names())
232 return event_names
233
234 def shape_str(state):
235 shape = BOX_SHAPES.get(state.kind, None)
236 if not shape:
237 return ''
238 return ',shape=%s' % shape
239
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200240 def color_str(state):
241 if state.color is None:
242 return ''
243 return ',color="%s"' % state.color
244
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100245 def __repr__(state):
246 return 'State(name=%r,short_name=%r,out=%d)' % (state.name, state.short_name, len(state.out_edges))
247
248class Fsm:
Neels Hofmeyra568af22017-10-23 04:03:19 +0200249 def __init__(fsm, struct_name, string_name, states_struct_name, from_file=None):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100250 fsm.states = []
251 fsm.struct_name = struct_name
Neels Hofmeyra568af22017-10-23 04:03:19 +0200252 fsm.string_name = string_name
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100253 fsm.states_struct_name = states_struct_name
254 fsm.from_file = from_file
255 fsm.action_funcs = set()
256 fsm.event_names = set()
Neels Hofmeyra568af22017-10-23 04:03:19 +0200257 fsm.dot_name = fsm.all_names_sanitized()
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100258
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200259 def __repr__(fsm):
260 return str(fsm)
261
262 def __str__(fsm):
263 return 'Fsm(%r,%r)' % (fsm.struct_name, fsm.from_file)
264
Neels Hofmeyr71f781c2018-03-26 15:01:38 +0200265 def parse_states(fsm, src, c_file):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100266 state = None
267 started = None
268
269 IN_EVENTS = 'events'
270 OUT_STATES = 'states'
271
272 lines = src.splitlines()
273
274 for line in lines:
275 state_name = state_starts(line)
276 if state_name:
Neels Hofmeyr71f781c2018-03-26 15:01:38 +0200277 state = fsm.find_state_by_name(state_name)
278 if state is not None:
279 if c_file is fsm.from_file:
280 print('ERROR: fsm %r has multiple definitions of state %r' % (fsm, state_name))
281 else:
282 print('ERROR: it appears two FSMs with identical name %r exist in %r and %r'
283 % (fsm.struct_name, fsm.from_file, c_file))
284 state = None
285 continue
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100286 state = State()
287 fsm.states.append(state)
288 started = None
289 state.name = state_name
290
291 if in_event_starts(line):
292 started = IN_EVENTS
293 if out_state_starts(line):
294 started = OUT_STATES
295
296 if not state or not started:
297 continue
298
299 tokens = states_or_events(line)
300 if started == IN_EVENTS:
301 state.in_event_names.extend(tokens)
302 elif started == OUT_STATES:
303 state.out_state_names.extend(tokens)
304 else:
305 err('ignoring: %r' % tokens)
306
307 a = parse_action(line)
308 if a:
309 state.action = a
310
311
312 for state in fsm.states:
313 if state.action:
314 fsm.action_funcs.add(state.action)
315 if state.in_event_names:
316 fsm.event_names.update(state.in_event_names)
317
318 fsm.make_states_short_names()
319 fsm.ref_out_states()
320
321 def make_states_short_names(fsm):
322 p = common_prefix([s.name for s in fsm.states])
323 for s in fsm.states:
324 s.short_name = s.name[len(p):]
325 return p
326
327 def make_events_short_names(fsm):
328 p = common_prefix(fsm.event_names)
329 for state in fsm.states:
330 for edge in state.out_edges:
331 for event in edge.events:
332 event.short_name = event.name[len(p):]
333
334 def ref_out_states(fsm):
335 for state in fsm.states:
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200336 for out_state_name in state.out_state_names:
337 out_state = fsm.find_state_by_name(out_state_name, False)
338 if out_state is None:
339 print('ERROR: fsm %r has a transition to state not part of the FSM: %r'
340 % (fsm, out_state_name))
341 out_state = fsm.have_state(out_state_name, KIND_STATE, color='red')
342 state.add_out_edge(Edge(out_state))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100343
344 def find_state_by_name(fsm, name, strict=False):
345 for state in fsm.states:
346 if state.name == name:
347 return state
348 if strict:
349 raise Exception("State not found: %r" % name);
350 return None
351
352 def find_state_by_action(fsm, action):
353 for state in fsm.states:
354 if state.action == action:
355 return state
356 return None
357
358 def add_special_state(fsm, additional_states, name, in_state=None,
359 out_state=None, event_name=None, kind=KIND_FUNC,
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200360 state_action=None, label=None, edge_action=None,
361 style='dotted', arrow_head=None):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100362 additional_state = None
363 for s in additional_states:
364 if s.short_name == name:
365 additional_state = s
366 break;
367
368 if not additional_state:
369 for s in fsm.states:
370 if s.short_name == name:
371 additional_state = s
372 break;
373
374 if kind == KIND_FUNC and not state_action:
375 state_action = name
376
377 if not additional_state:
378 additional_state = State()
379 additional_state.short_name = name
380 additional_state.action = state_action
381 additional_state.kind = kind
382 additional_state.label = label
383 additional_states.append(additional_state)
384
385 if out_state:
386 additional_state.out_state_names.append(out_state.name)
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200387 additional_state.add_out_edge(Edge(out_state, event_name, style=style,
388 action=edge_action, arrow_head=arrow_head))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100389
390 if in_state:
391 in_state.out_state_names.append(additional_state.name)
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200392 in_state.add_out_edge(Edge(additional_state, event_name, style=style,
393 action=edge_action, arrow_head=arrow_head))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100394
395
396 def find_event_edges(fsm, c_files):
397 # enrich state transitions between the states with event labels
398 func_to_state_transitions = listdict()
399 for c_file in c_files:
400 func_to_state_transitions.update( c_file.find_state_transitions(fsm.event_names) )
401
402 # edges between explicit states
403 for state in fsm.states:
404 transitions = func_to_state_transitions.get(state.action)
405 if not transitions:
406 continue
407
408 for to_state_name, event_name in transitions:
409 if not event_name:
410 continue
Neels Hofmeyrfcf79922018-03-25 01:03:26 +0100411 found = False
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100412 for out_edge in state.out_edges:
413 if out_edge.to_state.name == to_state_name:
414 out_edge.add_event_name(event_name)
Neels Hofmeyrfcf79922018-03-25 01:03:26 +0100415 found = True
416 if not found:
417 sys.stderr.write(
418 "ERROR: %s() triggers a transition to %s, but this is not allowed by the FSM definition\n"
419 % (state.action, to_state_name))
420 state.add_out_edge(Edge(fsm.find_state_by_name(to_state_name, True), event_name,
421 color='red'))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100422
423 additional_states = []
424
425
426 # functions that aren't state actions but still effect state transitions
427 for func_name, transitions in func_to_state_transitions.items():
428 if func_name in fsm.action_funcs:
429 continue
430 for to_state_name, event_name in transitions:
431 to_state = fsm.find_state_by_name(to_state_name)
432 if not to_state:
433 continue
434 fsm.add_special_state(additional_states, func_name, None, to_state, event_name)
435
436
437 event_sources = c_files.find_event_sources(fsm.event_names)
438
439 for state in fsm.states:
440
441 for in_event_name in state.in_event_names:
442 funcs_for_in_event = event_sources.get(in_event_name)
443 if not funcs_for_in_event:
444 continue
445
446 found = False
447 for out_edge in state.out_edges:
448 if out_edge.has_event_name(in_event_name):
449 out_edge.action = r'\n'.join([(f + '()') for f in funcs_for_in_event
450 if f != state.action])
451
452 # if any functions that don't belong to a state trigger events, add
453 # them to the graph as well
454 additional_funcs = [f for f in funcs_for_in_event if f not in fsm.action_funcs]
455 for af in additional_funcs:
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200456 fsm.add_special_state(additional_states, af, None, state, in_event_name,
457 arrow_head='halfopen')
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100458
459 fsm.states.extend(additional_states)
460
461 # do any existing action functions by chance call other action functions?
462 for state in fsm.states:
463 if not state.action:
464 continue
465 callers = c_files.find_callers(state.action)
466 if not callers:
467 continue
468 for other_state in fsm.states:
469 if other_state.action in callers:
470 other_state.add_out_edge(Edge(state, None, 'dotted'))
471
472 def add_fsm_alloc(fsm, c_files):
473
474 allocating_funcs = []
475 for c_file in c_files:
476 allocating_funcs.extend(c_file.fsm_allocators.get(fsm.struct_name, []))
477
478 starting_state = None
479 if fsm.states:
480 # assume the first state starts
481 starting_state = fsm.states[0]
482
483 additional_states = []
484 for func_name in allocating_funcs:
485 fsm.add_special_state(additional_states, func_name, None, starting_state)
486
487 fsm.states.extend(additional_states)
488
489 def add_cross_fsm_links(fsm, fsms, c_files, fsm_meta):
490 for state in fsm.states:
491 if not state.action:
492 continue
493 if state.kind == KIND_FSM:
494 continue
495 callers = c_files.find_callers(state.action)
496
497 if state.kind == KIND_FUNC:
498 callers.append(state.action)
499
500 if not callers:
501 continue
502
503 for caller in callers:
504 for calling_fsm in fsms:
505 if calling_fsm is fsm:
506 continue
507 calling_state = calling_fsm.find_state_by_action(caller)
508 if not calling_state:
509 continue
510 if calling_state.kind == KIND_FSM:
511 continue
512
513 label = None
514 if state.kind == KIND_STATE:
515 label=fsm.struct_name + ': ' + state.short_name
516 edge_action = caller
517 if calling_state.action == edge_action:
518 edge_action = None
Neels Hofmeyra568af22017-10-23 04:03:19 +0200519 calling_fsm.add_special_state(calling_fsm.states, fsm.dot_name,
520 calling_state, kind=KIND_FSM, edge_action=edge_action, label=' '.join(fsm.all_names()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100521
522 label = None
523 if calling_state.kind == KIND_STATE:
524 label=calling_fsm.struct_name + ': ' + calling_state.short_name
525 edge_action = caller
526 if state.action == edge_action:
527 edge_action = None
Neels Hofmeyra568af22017-10-23 04:03:19 +0200528 fsm.add_special_state(fsm.states, calling_fsm.dot_name, None,
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100529 state, kind=KIND_FSM, edge_action=edge_action,
530 label=label)
531
532 # meta overview
Neels Hofmeyra568af22017-10-23 04:03:19 +0200533 meta_called_fsm = fsm_meta.have_state(fsm.dot_name, KIND_FSM)
534 meta_calling_fsm = fsm_meta.have_state(calling_fsm.dot_name, KIND_FSM)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100535 meta_calling_fsm.add_out_edge(Edge(meta_called_fsm))
536
537
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200538 def have_state(fsm, name, kind=KIND_STATE, color=None):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100539 state = fsm.find_state_by_name(name)
540 if not state:
541 state = State()
542 state.name = name
543 state.short_name = name
544 state.kind = kind
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200545 state.color = color
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100546 fsm.states.append(state)
547 return state
548
549 def to_dot(fsm):
550 out = ['digraph G {', 'rankdir=LR;']
551
552 for state in fsm.states:
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200553 out.append('%s [label="%s"%s%s]' % (state.short_name, state.get_label(),
554 state.shape_str(), state.color_str()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100555
556 for state in fsm.states:
557 for out_edge in state.out_edges:
558 attrs = []
559 labels = []
560 if out_edge.events:
561 labels.extend(out_edge.event_labels())
562 if out_edge.actions:
563 labels.extend(out_edge.action_labels())
564 if labels:
Neels Hofmeyr75ee4e82018-03-25 03:08:28 +0200565 label = r'\n'.join(labels)
566 else:
567 label = '-'
568 attrs.append('label="%s"' % label)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100569 if out_edge.style:
570 attrs.append('style=%s'% out_edge.style)
Neels Hofmeyrfcf79922018-03-25 01:03:26 +0100571 if out_edge.color:
572 attrs.append('color=%s'% out_edge.color)
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200573 if out_edge.arrow_head:
574 attrs.append('arrowhead=%s'% out_edge.arrow_head)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100575 attrs_str = ''
576 if attrs:
577 attrs_str = ' [%s]' % (','.join(attrs))
578 out.append('%s->%s%s' % (state.short_name, out_edge.to_state.short_name, attrs_str))
579
580 out.append('}\n')
581
582 return '\n'.join(out)
583
Neels Hofmeyra568af22017-10-23 04:03:19 +0200584 def all_names(fsm):
585 n = []
586 if fsm.from_file:
587 n.append(os.path.basename(fsm.from_file.path))
588 if fsm.struct_name:
589 n.append(fsm.struct_name)
590 if fsm.string_name:
591 n.append(fsm.string_name)
592 return n
593
594 def all_names_sanitized(fsm, sep='_'):
595 n = sep.join(fsm.all_names())
596 n = re_insane_dot_name_chars.sub('_', n)
597 return n
598
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100599 def write_dot_file(fsm):
Neels Hofmeyra568af22017-10-23 04:03:19 +0200600 dot_path = '%s.dot' % ('_'.join(fsm.all_names()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100601 f = open(dot_path, 'w')
602 f.write(fsm.to_dot())
603 f.close()
604 print(dot_path)
605
606
607re_fsm = re.compile(r'struct osmo_fsm ([a-z_][a-z_0-9]*) =')
Neels Hofmeyra568af22017-10-23 04:03:19 +0200608re_fsm_string_name = re.compile(r'\bname = "([^"]*)"')
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100609re_fsm_states_struct_name = re.compile(r'\bstates = ([a-z_][a-z_0-9]*)\W*,')
610re_fsm_states = re.compile(r'struct osmo_fsm_state ([a-z_][a-z_0-9]*)\[\] =')
611re_func = re.compile(r'(\b[a-z_][a-z_0-9]*\b)\([^)]*\)\W*^{', re.MULTILINE)
612re_state_trigger = re.compile(r'osmo_fsm_inst_state_chg\([^,]+,\W*([A-Z_][A-Z_0-9]*)\W*,', re.M)
613re_fsm_alloc = re.compile(r'osmo_fsm_inst_alloc[_child]*\(\W*&([a-z_][a-z_0-9]*),', re.M)
614re_fsm_event_dispatch = re.compile(r'osmo_fsm_inst_dispatch\(\W*[^,]+,\W*([A-Z_][A-Z_0-9]*)\W*,', re.M)
Neels Hofmeyrbb22df32018-03-25 01:03:26 +0100615re_comment_multiline = re.compile(r'/\*.*?\*/', re.M | re.S)
616re_comment_single_line = re.compile(r'//.*$', re.M | re.S)
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200617re_break = re.compile(r'^\W*\bbreak;', re.M)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100618
619class CFile():
620 def __init__(c_file, path):
621 c_file.path = path
622 c_file.src = open(path).read()
623 c_file.funcs = {}
624 c_file.fsm_allocators = listdict()
625
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200626 def __repr__(c_file):
627 return str(c_file)
628
629 def __str__(c_file):
630 return 'CFile(%r)' % c_file.path
631
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100632 def extract_block(c_file, brace_open, brace_close, start):
633 pos = 0
634 try:
635 src = c_file.src
636 block_start = src.find(brace_open, start)
637
638 pos = block_start
639 level = 1
640 while level > 0:
641 pos += 1
642 if src[pos] == brace_open:
643 level += 1
644 elif src[pos] == brace_close:
645 level -= 1
646
647 return src[block_start+1:pos]
648 except:
649 print("Error while trying to extract a code block from %r char pos %d" % (c_file.path, pos))
650 print("Block start at char pos %d" % block_start)
651 try:
652 print(src[block_start - 20 : block_start + 20])
653 print('...')
654 print(src[pos - 20 : pos + 20])
655 except:
656 pass
657 return ''
658
659
660 def find_fsms(c_file):
661 fsms = []
662 for m in re_fsm.finditer(c_file.src):
663 struct_name = m.group(1)
664 struct_def = c_file.extract_block('{', '}', m.start())
Neels Hofmeyra568af22017-10-23 04:03:19 +0200665 string_name = (re_fsm_string_name.findall(struct_def) or [None])[0]
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100666 states_struct_name = re_fsm_states_struct_name.findall(struct_def)[0]
Neels Hofmeyra568af22017-10-23 04:03:19 +0200667 fsm = Fsm(struct_name, string_name, states_struct_name, c_file)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100668 fsms.append(fsm)
669 return fsms
670
671 def find_fsm_states(c_file, fsms):
672 for m in re_fsm_states.finditer(c_file.src):
673 states_struct_name = m.group(1)
674 for fsm in fsms:
675 if states_struct_name == fsm.states_struct_name:
Neels Hofmeyr71f781c2018-03-26 15:01:38 +0200676 fsm.parse_states(c_file.extract_block('{', '}', m.start()), c_file)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100677
678 def parse_functions(c_file):
679 funcs = {}
680 for m in re_func.finditer(c_file.src):
681 name = m.group(1)
682 func_src = c_file.extract_block('{', '}', m.start())
Neels Hofmeyrbb22df32018-03-25 01:03:26 +0100683 func_src = ''.join(re_comment_multiline.split(func_src))
684 func_src = ''.join(re_comment_single_line.split(func_src))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100685 funcs[name] = func_src
686 c_file.funcs = funcs
687 c_file.find_fsm_allocators()
688
689 def find_callers(c_file, func_name):
690 func_call = func_name + '('
691 callers = []
692 for func_name, src in c_file.funcs.items():
693 if src.find(func_call) >= 0:
694 callers.append(func_name)
695 return callers
696
697 def find_fsm_allocators(c_file):
698 c_file.fsm_allocators = listdict()
699 for func_name, src in c_file.funcs.items():
700 for m in re_fsm_alloc.finditer(src):
701 fsm_struct_name = m.group(1)
702 c_file.fsm_allocators.add(fsm_struct_name, func_name)
703
704 def find_state_transitions(c_file, event_names):
705 TO_STATE = 'TO_STATE'
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200706 IF_EVENT = 'IF_EVENT'
707 CASE_EVENT = 'CASE_EVENT'
708 BREAK = 'BREAK'
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100709 func_to_state_transitions = listdict()
710
711 for func_name, src in c_file.funcs.items():
712 found_tokens = []
713
714 for m in re_state_trigger.finditer(src):
715 to_state = m.group(1)
716 found_tokens.append((m.start(), TO_STATE, to_state))
717
718 for event in event_names:
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200719 re_event = re.compile(r'\bif\w*\(.*\b(' + event + r')\b')
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100720 for m in re_event.finditer(src):
721 event = m.group(1)
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200722 found_tokens.append((m.start(), IF_EVENT, event))
723
724 re_event = re.compile(r'^\W*case\W\W*\b(' + event + r'):', re.M)
725 for m in re_event.finditer(src):
726 event = m.group(1)
727 found_tokens.append((m.start(), CASE_EVENT, event))
728
729 for m in re_break.finditer(src):
730 found_tokens.append((m.start(), BREAK, 'break'))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100731
732 found_tokens = sorted(found_tokens)
733
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200734 last_events = []
735 saw_break = True
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100736 for start, kind, name in found_tokens:
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200737 if kind == IF_EVENT:
738 last_events = [name]
739 saw_break = True
740 elif kind == CASE_EVENT:
741 if saw_break:
742 last_events = []
743 saw_break = False
744 last_events.append(name)
745 elif kind == BREAK:
746 saw_break = True
747 elif kind == TO_STATE:
748 for event in (last_events or [None]):
749 func_to_state_transitions.add(func_name, (name, event))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100750
751 return func_to_state_transitions
752
753
754 def find_event_sources(c_file, event_names):
755 c_file.event_sources = listdict()
756 for func_name, src in c_file.funcs.items():
757 for m in re_fsm_event_dispatch.finditer(src):
758 event_name = m.group(1)
759 c_file.event_sources.add(event_name, func_name)
760
761class CFiles(list):
762
763 def find_callers(c_files, func_name):
764 callers = []
765 for c_file in c_files:
766 callers.extend(c_file.find_callers(func_name))
767 return callers
768
769 def find_func_to_state_transitions(c_files):
770 func_to_state_transitions = listdict()
771 for c_file in c_files:
772 func_to_state_transitions.update( c_file.find_state_transitions(fsm.event_names) )
773 return func_to_state_transitions
774
775 def find_event_sources(c_files, event_names):
776 event_sources = listdict()
777 for c_file in c_files:
778 for event, sources in c_file.event_sources.items():
779 if event in event_names:
780 event_sources.extend(event, sources)
781 return event_sources
782
783c_files = CFiles()
784paths_seen = set()
785for path in sys.argv[1:]:
786 if path in paths_seen:
787 continue
788 paths_seen.add(path)
789 c_file = CFile(path)
790 c_files.append(c_file)
791
792for c_file in c_files:
793 c_file.parse_functions()
794
795fsms = []
796for c_file in c_files:
797 fsms.extend(c_file.find_fsms())
798
Neels Hofmeyr71f781c2018-03-26 15:01:38 +0200799for fsm1 in fsms:
800 for fsm2 in fsms:
801 if fsm1 is fsm2:
802 continue
803 if fsm1.states_struct_name == fsm2.states_struct_name:
804 print('ERROR: two distinct FSMs share the same states-struct name: %r and %r both use %r'
805 % (fsm1, fsm2, fsm1.states_struct_name))
806
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100807for c_file in c_files:
808 c_file.find_fsm_states(fsms)
809 c_file.find_event_sources(fsms)
810
811for fsm in fsms:
812 fsm.find_event_edges(c_files)
813 fsm.add_fsm_alloc(c_files)
814
Neels Hofmeyra568af22017-10-23 04:03:19 +0200815fsm_meta = Fsm("meta", None, "meta")
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100816for fsm in fsms:
817 fsm.add_cross_fsm_links(fsms, c_files, fsm_meta)
818
819for fsm in fsms:
820 fsm.make_events_short_names()
821
822for fsm in fsms:
823 fsm.write_dot_file()
824
825fsm_meta.write_dot_file()
826
827
828# vim: tabstop=2 shiftwidth=2 expandtab