blob: 96f7e005e0429b1a4c6ecd6ce11ca407d97cd28d [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 Hofmeyr0898a002016-11-16 14:36:29 +0100265 def parse_states(fsm, src):
266 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:
277 state = State()
278 fsm.states.append(state)
279 started = None
280 state.name = state_name
281
282 if in_event_starts(line):
283 started = IN_EVENTS
284 if out_state_starts(line):
285 started = OUT_STATES
286
287 if not state or not started:
288 continue
289
290 tokens = states_or_events(line)
291 if started == IN_EVENTS:
292 state.in_event_names.extend(tokens)
293 elif started == OUT_STATES:
294 state.out_state_names.extend(tokens)
295 else:
296 err('ignoring: %r' % tokens)
297
298 a = parse_action(line)
299 if a:
300 state.action = a
301
302
303 for state in fsm.states:
304 if state.action:
305 fsm.action_funcs.add(state.action)
306 if state.in_event_names:
307 fsm.event_names.update(state.in_event_names)
308
309 fsm.make_states_short_names()
310 fsm.ref_out_states()
311
312 def make_states_short_names(fsm):
313 p = common_prefix([s.name for s in fsm.states])
314 for s in fsm.states:
315 s.short_name = s.name[len(p):]
316 return p
317
318 def make_events_short_names(fsm):
319 p = common_prefix(fsm.event_names)
320 for state in fsm.states:
321 for edge in state.out_edges:
322 for event in edge.events:
323 event.short_name = event.name[len(p):]
324
325 def ref_out_states(fsm):
326 for state in fsm.states:
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200327 for out_state_name in state.out_state_names:
328 out_state = fsm.find_state_by_name(out_state_name, False)
329 if out_state is None:
330 print('ERROR: fsm %r has a transition to state not part of the FSM: %r'
331 % (fsm, out_state_name))
332 out_state = fsm.have_state(out_state_name, KIND_STATE, color='red')
333 state.add_out_edge(Edge(out_state))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100334
335 def find_state_by_name(fsm, name, strict=False):
336 for state in fsm.states:
337 if state.name == name:
338 return state
339 if strict:
340 raise Exception("State not found: %r" % name);
341 return None
342
343 def find_state_by_action(fsm, action):
344 for state in fsm.states:
345 if state.action == action:
346 return state
347 return None
348
349 def add_special_state(fsm, additional_states, name, in_state=None,
350 out_state=None, event_name=None, kind=KIND_FUNC,
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200351 state_action=None, label=None, edge_action=None,
352 style='dotted', arrow_head=None):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100353 additional_state = None
354 for s in additional_states:
355 if s.short_name == name:
356 additional_state = s
357 break;
358
359 if not additional_state:
360 for s in fsm.states:
361 if s.short_name == name:
362 additional_state = s
363 break;
364
365 if kind == KIND_FUNC and not state_action:
366 state_action = name
367
368 if not additional_state:
369 additional_state = State()
370 additional_state.short_name = name
371 additional_state.action = state_action
372 additional_state.kind = kind
373 additional_state.label = label
374 additional_states.append(additional_state)
375
376 if out_state:
377 additional_state.out_state_names.append(out_state.name)
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200378 additional_state.add_out_edge(Edge(out_state, event_name, style=style,
379 action=edge_action, arrow_head=arrow_head))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100380
381 if in_state:
382 in_state.out_state_names.append(additional_state.name)
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200383 in_state.add_out_edge(Edge(additional_state, event_name, style=style,
384 action=edge_action, arrow_head=arrow_head))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100385
386
387 def find_event_edges(fsm, c_files):
388 # enrich state transitions between the states with event labels
389 func_to_state_transitions = listdict()
390 for c_file in c_files:
391 func_to_state_transitions.update( c_file.find_state_transitions(fsm.event_names) )
392
393 # edges between explicit states
394 for state in fsm.states:
395 transitions = func_to_state_transitions.get(state.action)
396 if not transitions:
397 continue
398
399 for to_state_name, event_name in transitions:
400 if not event_name:
401 continue
Neels Hofmeyrfcf79922018-03-25 01:03:26 +0100402 found = False
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100403 for out_edge in state.out_edges:
404 if out_edge.to_state.name == to_state_name:
405 out_edge.add_event_name(event_name)
Neels Hofmeyrfcf79922018-03-25 01:03:26 +0100406 found = True
407 if not found:
408 sys.stderr.write(
409 "ERROR: %s() triggers a transition to %s, but this is not allowed by the FSM definition\n"
410 % (state.action, to_state_name))
411 state.add_out_edge(Edge(fsm.find_state_by_name(to_state_name, True), event_name,
412 color='red'))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100413
414 additional_states = []
415
416
417 # functions that aren't state actions but still effect state transitions
418 for func_name, transitions in func_to_state_transitions.items():
419 if func_name in fsm.action_funcs:
420 continue
421 for to_state_name, event_name in transitions:
422 to_state = fsm.find_state_by_name(to_state_name)
423 if not to_state:
424 continue
425 fsm.add_special_state(additional_states, func_name, None, to_state, event_name)
426
427
428 event_sources = c_files.find_event_sources(fsm.event_names)
429
430 for state in fsm.states:
431
432 for in_event_name in state.in_event_names:
433 funcs_for_in_event = event_sources.get(in_event_name)
434 if not funcs_for_in_event:
435 continue
436
437 found = False
438 for out_edge in state.out_edges:
439 if out_edge.has_event_name(in_event_name):
440 out_edge.action = r'\n'.join([(f + '()') for f in funcs_for_in_event
441 if f != state.action])
442
443 # if any functions that don't belong to a state trigger events, add
444 # them to the graph as well
445 additional_funcs = [f for f in funcs_for_in_event if f not in fsm.action_funcs]
446 for af in additional_funcs:
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200447 fsm.add_special_state(additional_states, af, None, state, in_event_name,
448 arrow_head='halfopen')
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100449
450 fsm.states.extend(additional_states)
451
452 # do any existing action functions by chance call other action functions?
453 for state in fsm.states:
454 if not state.action:
455 continue
456 callers = c_files.find_callers(state.action)
457 if not callers:
458 continue
459 for other_state in fsm.states:
460 if other_state.action in callers:
461 other_state.add_out_edge(Edge(state, None, 'dotted'))
462
463 def add_fsm_alloc(fsm, c_files):
464
465 allocating_funcs = []
466 for c_file in c_files:
467 allocating_funcs.extend(c_file.fsm_allocators.get(fsm.struct_name, []))
468
469 starting_state = None
470 if fsm.states:
471 # assume the first state starts
472 starting_state = fsm.states[0]
473
474 additional_states = []
475 for func_name in allocating_funcs:
476 fsm.add_special_state(additional_states, func_name, None, starting_state)
477
478 fsm.states.extend(additional_states)
479
480 def add_cross_fsm_links(fsm, fsms, c_files, fsm_meta):
481 for state in fsm.states:
482 if not state.action:
483 continue
484 if state.kind == KIND_FSM:
485 continue
486 callers = c_files.find_callers(state.action)
487
488 if state.kind == KIND_FUNC:
489 callers.append(state.action)
490
491 if not callers:
492 continue
493
494 for caller in callers:
495 for calling_fsm in fsms:
496 if calling_fsm is fsm:
497 continue
498 calling_state = calling_fsm.find_state_by_action(caller)
499 if not calling_state:
500 continue
501 if calling_state.kind == KIND_FSM:
502 continue
503
504 label = None
505 if state.kind == KIND_STATE:
506 label=fsm.struct_name + ': ' + state.short_name
507 edge_action = caller
508 if calling_state.action == edge_action:
509 edge_action = None
Neels Hofmeyra568af22017-10-23 04:03:19 +0200510 calling_fsm.add_special_state(calling_fsm.states, fsm.dot_name,
511 calling_state, kind=KIND_FSM, edge_action=edge_action, label=' '.join(fsm.all_names()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100512
513 label = None
514 if calling_state.kind == KIND_STATE:
515 label=calling_fsm.struct_name + ': ' + calling_state.short_name
516 edge_action = caller
517 if state.action == edge_action:
518 edge_action = None
Neels Hofmeyra568af22017-10-23 04:03:19 +0200519 fsm.add_special_state(fsm.states, calling_fsm.dot_name, None,
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100520 state, kind=KIND_FSM, edge_action=edge_action,
521 label=label)
522
523 # meta overview
Neels Hofmeyra568af22017-10-23 04:03:19 +0200524 meta_called_fsm = fsm_meta.have_state(fsm.dot_name, KIND_FSM)
525 meta_calling_fsm = fsm_meta.have_state(calling_fsm.dot_name, KIND_FSM)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100526 meta_calling_fsm.add_out_edge(Edge(meta_called_fsm))
527
528
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200529 def have_state(fsm, name, kind=KIND_STATE, color=None):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100530 state = fsm.find_state_by_name(name)
531 if not state:
532 state = State()
533 state.name = name
534 state.short_name = name
535 state.kind = kind
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200536 state.color = color
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100537 fsm.states.append(state)
538 return state
539
540 def to_dot(fsm):
541 out = ['digraph G {', 'rankdir=LR;']
542
543 for state in fsm.states:
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200544 out.append('%s [label="%s"%s%s]' % (state.short_name, state.get_label(),
545 state.shape_str(), state.color_str()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100546
547 for state in fsm.states:
548 for out_edge in state.out_edges:
549 attrs = []
550 labels = []
551 if out_edge.events:
552 labels.extend(out_edge.event_labels())
553 if out_edge.actions:
554 labels.extend(out_edge.action_labels())
555 if labels:
Neels Hofmeyr75ee4e82018-03-25 03:08:28 +0200556 label = r'\n'.join(labels)
557 else:
558 label = '-'
559 attrs.append('label="%s"' % label)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100560 if out_edge.style:
561 attrs.append('style=%s'% out_edge.style)
Neels Hofmeyrfcf79922018-03-25 01:03:26 +0100562 if out_edge.color:
563 attrs.append('color=%s'% out_edge.color)
Neels Hofmeyr536534a2018-03-25 04:15:37 +0200564 if out_edge.arrow_head:
565 attrs.append('arrowhead=%s'% out_edge.arrow_head)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100566 attrs_str = ''
567 if attrs:
568 attrs_str = ' [%s]' % (','.join(attrs))
569 out.append('%s->%s%s' % (state.short_name, out_edge.to_state.short_name, attrs_str))
570
571 out.append('}\n')
572
573 return '\n'.join(out)
574
Neels Hofmeyra568af22017-10-23 04:03:19 +0200575 def all_names(fsm):
576 n = []
577 if fsm.from_file:
578 n.append(os.path.basename(fsm.from_file.path))
579 if fsm.struct_name:
580 n.append(fsm.struct_name)
581 if fsm.string_name:
582 n.append(fsm.string_name)
583 return n
584
585 def all_names_sanitized(fsm, sep='_'):
586 n = sep.join(fsm.all_names())
587 n = re_insane_dot_name_chars.sub('_', n)
588 return n
589
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100590 def write_dot_file(fsm):
Neels Hofmeyra568af22017-10-23 04:03:19 +0200591 dot_path = '%s.dot' % ('_'.join(fsm.all_names()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100592 f = open(dot_path, 'w')
593 f.write(fsm.to_dot())
594 f.close()
595 print(dot_path)
596
597
598re_fsm = re.compile(r'struct osmo_fsm ([a-z_][a-z_0-9]*) =')
Neels Hofmeyra568af22017-10-23 04:03:19 +0200599re_fsm_string_name = re.compile(r'\bname = "([^"]*)"')
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100600re_fsm_states_struct_name = re.compile(r'\bstates = ([a-z_][a-z_0-9]*)\W*,')
601re_fsm_states = re.compile(r'struct osmo_fsm_state ([a-z_][a-z_0-9]*)\[\] =')
602re_func = re.compile(r'(\b[a-z_][a-z_0-9]*\b)\([^)]*\)\W*^{', re.MULTILINE)
603re_state_trigger = re.compile(r'osmo_fsm_inst_state_chg\([^,]+,\W*([A-Z_][A-Z_0-9]*)\W*,', re.M)
604re_fsm_alloc = re.compile(r'osmo_fsm_inst_alloc[_child]*\(\W*&([a-z_][a-z_0-9]*),', re.M)
605re_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 +0100606re_comment_multiline = re.compile(r'/\*.*?\*/', re.M | re.S)
607re_comment_single_line = re.compile(r'//.*$', re.M | re.S)
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200608re_break = re.compile(r'^\W*\bbreak;', re.M)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100609
610class CFile():
611 def __init__(c_file, path):
612 c_file.path = path
613 c_file.src = open(path).read()
614 c_file.funcs = {}
615 c_file.fsm_allocators = listdict()
616
Neels Hofmeyr338d1742018-03-26 14:59:18 +0200617 def __repr__(c_file):
618 return str(c_file)
619
620 def __str__(c_file):
621 return 'CFile(%r)' % c_file.path
622
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100623 def extract_block(c_file, brace_open, brace_close, start):
624 pos = 0
625 try:
626 src = c_file.src
627 block_start = src.find(brace_open, start)
628
629 pos = block_start
630 level = 1
631 while level > 0:
632 pos += 1
633 if src[pos] == brace_open:
634 level += 1
635 elif src[pos] == brace_close:
636 level -= 1
637
638 return src[block_start+1:pos]
639 except:
640 print("Error while trying to extract a code block from %r char pos %d" % (c_file.path, pos))
641 print("Block start at char pos %d" % block_start)
642 try:
643 print(src[block_start - 20 : block_start + 20])
644 print('...')
645 print(src[pos - 20 : pos + 20])
646 except:
647 pass
648 return ''
649
650
651 def find_fsms(c_file):
652 fsms = []
653 for m in re_fsm.finditer(c_file.src):
654 struct_name = m.group(1)
655 struct_def = c_file.extract_block('{', '}', m.start())
Neels Hofmeyra568af22017-10-23 04:03:19 +0200656 string_name = (re_fsm_string_name.findall(struct_def) or [None])[0]
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100657 states_struct_name = re_fsm_states_struct_name.findall(struct_def)[0]
Neels Hofmeyra568af22017-10-23 04:03:19 +0200658 fsm = Fsm(struct_name, string_name, states_struct_name, c_file)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100659 fsms.append(fsm)
660 return fsms
661
662 def find_fsm_states(c_file, fsms):
663 for m in re_fsm_states.finditer(c_file.src):
664 states_struct_name = m.group(1)
665 for fsm in fsms:
666 if states_struct_name == fsm.states_struct_name:
667 fsm.parse_states(c_file.extract_block('{', '}', m.start()))
668
669 def parse_functions(c_file):
670 funcs = {}
671 for m in re_func.finditer(c_file.src):
672 name = m.group(1)
673 func_src = c_file.extract_block('{', '}', m.start())
Neels Hofmeyrbb22df32018-03-25 01:03:26 +0100674 func_src = ''.join(re_comment_multiline.split(func_src))
675 func_src = ''.join(re_comment_single_line.split(func_src))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100676 funcs[name] = func_src
677 c_file.funcs = funcs
678 c_file.find_fsm_allocators()
679
680 def find_callers(c_file, func_name):
681 func_call = func_name + '('
682 callers = []
683 for func_name, src in c_file.funcs.items():
684 if src.find(func_call) >= 0:
685 callers.append(func_name)
686 return callers
687
688 def find_fsm_allocators(c_file):
689 c_file.fsm_allocators = listdict()
690 for func_name, src in c_file.funcs.items():
691 for m in re_fsm_alloc.finditer(src):
692 fsm_struct_name = m.group(1)
693 c_file.fsm_allocators.add(fsm_struct_name, func_name)
694
695 def find_state_transitions(c_file, event_names):
696 TO_STATE = 'TO_STATE'
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200697 IF_EVENT = 'IF_EVENT'
698 CASE_EVENT = 'CASE_EVENT'
699 BREAK = 'BREAK'
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100700 func_to_state_transitions = listdict()
701
702 for func_name, src in c_file.funcs.items():
703 found_tokens = []
704
705 for m in re_state_trigger.finditer(src):
706 to_state = m.group(1)
707 found_tokens.append((m.start(), TO_STATE, to_state))
708
709 for event in event_names:
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200710 re_event = re.compile(r'\bif\w*\(.*\b(' + event + r')\b')
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100711 for m in re_event.finditer(src):
712 event = m.group(1)
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200713 found_tokens.append((m.start(), IF_EVENT, event))
714
715 re_event = re.compile(r'^\W*case\W\W*\b(' + event + r'):', re.M)
716 for m in re_event.finditer(src):
717 event = m.group(1)
718 found_tokens.append((m.start(), CASE_EVENT, event))
719
720 for m in re_break.finditer(src):
721 found_tokens.append((m.start(), BREAK, 'break'))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100722
723 found_tokens = sorted(found_tokens)
724
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200725 last_events = []
726 saw_break = True
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100727 for start, kind, name in found_tokens:
Neels Hofmeyrec0f3342018-03-25 04:17:35 +0200728 if kind == IF_EVENT:
729 last_events = [name]
730 saw_break = True
731 elif kind == CASE_EVENT:
732 if saw_break:
733 last_events = []
734 saw_break = False
735 last_events.append(name)
736 elif kind == BREAK:
737 saw_break = True
738 elif kind == TO_STATE:
739 for event in (last_events or [None]):
740 func_to_state_transitions.add(func_name, (name, event))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100741
742 return func_to_state_transitions
743
744
745 def find_event_sources(c_file, event_names):
746 c_file.event_sources = listdict()
747 for func_name, src in c_file.funcs.items():
748 for m in re_fsm_event_dispatch.finditer(src):
749 event_name = m.group(1)
750 c_file.event_sources.add(event_name, func_name)
751
752class CFiles(list):
753
754 def find_callers(c_files, func_name):
755 callers = []
756 for c_file in c_files:
757 callers.extend(c_file.find_callers(func_name))
758 return callers
759
760 def find_func_to_state_transitions(c_files):
761 func_to_state_transitions = listdict()
762 for c_file in c_files:
763 func_to_state_transitions.update( c_file.find_state_transitions(fsm.event_names) )
764 return func_to_state_transitions
765
766 def find_event_sources(c_files, event_names):
767 event_sources = listdict()
768 for c_file in c_files:
769 for event, sources in c_file.event_sources.items():
770 if event in event_names:
771 event_sources.extend(event, sources)
772 return event_sources
773
774c_files = CFiles()
775paths_seen = set()
776for path in sys.argv[1:]:
777 if path in paths_seen:
778 continue
779 paths_seen.add(path)
780 c_file = CFile(path)
781 c_files.append(c_file)
782
783for c_file in c_files:
784 c_file.parse_functions()
785
786fsms = []
787for c_file in c_files:
788 fsms.extend(c_file.find_fsms())
789
790for c_file in c_files:
791 c_file.find_fsm_states(fsms)
792 c_file.find_event_sources(fsms)
793
794for fsm in fsms:
795 fsm.find_event_edges(c_files)
796 fsm.add_fsm_alloc(c_files)
797
Neels Hofmeyra568af22017-10-23 04:03:19 +0200798fsm_meta = Fsm("meta", None, "meta")
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100799for fsm in fsms:
800 fsm.add_cross_fsm_links(fsms, c_files, fsm_meta)
801
802for fsm in fsms:
803 fsm.make_events_short_names()
804
805for fsm in fsms:
806 fsm.write_dot_file()
807
808fsm_meta.write_dot_file()
809
810
811# vim: tabstop=2 shiftwidth=2 expandtab