blob: 3e9678d324f2386e2c60fb72de6fa4f837c31ef8 [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:
140 def __init__(edge, to_state, event_name=None, style=None, action=None):
141 edge.to_state = to_state
142 edge.style = style
143 edge.events = []
144 edge.actions = []
145 edge.add_event_name(event_name)
146 edge.add_action(action)
147
148 def add_event_name(edge, event_name):
149 if not event_name:
150 return
151 edge.add_event(Event(event_name))
152
153 def add_event(edge, event):
154 if not event:
155 return
156 if event in edge.events:
157 return
158 edge.events.append(event)
159
160 def add_events(edge, events):
161 for event in events:
162 edge.add_event(event)
163
164 def add_action(edge, action):
165 if not action or action in edge.actions:
166 return
167 edge.actions.append(action)
168
169 def add_actions(edge, actions):
170 for action in actions:
171 edge.add_action(action)
172
173 def event_names(edge):
174 return sorted([event.name for event in edge.events])
175
176 def event_labels(edge):
177 return sorted([event.short_name for event in edge.events])
178
179 def action_labels(edge):
180 return sorted([action + '()' for action in edge.actions])
181
182 def has_event_name(edge, event_name):
183 return event_name in edge.event_names()
184
185class State:
186 name = None
187 short_name = None
188 action = None
189 label = None
190 in_event_names = None
191 out_state_names = None
192 out_edges = None
193 kind = None
194
195 def __init__(state):
196 state.in_event_names = []
197 state.out_state_names = []
198 state.out_edges = []
199 state.kind = KIND_STATE
200
201 def add_out_edge(state, edge):
202 for out_edge in state.out_edges:
203 if out_edge.to_state is edge.to_state:
204 if out_edge.style == edge.style:
205 out_edge.add_events(edge.events)
206 out_edge.add_actions(edge.actions)
207 return
208 # sanity
209 if out_edge.to_state.get_label() == edge.to_state.get_label():
210 raise Exception('Two distinct states exist with identical labels.')
211 state.out_edges.append(edge)
212
213 def get_label(state):
214 if state.label:
215 return state.label
216 l = [state.short_name]
217 if state.action:
218 if state.short_name == state.action:
219 l = []
220 l.append(state.action + '()')
221 return r'\n'.join(l)
222
223 def event_names(state):
224 event_names = []
225 for out_edge in state.out_edges:
226 event_names.extend(out_edge.event_names())
227 return event_names
228
229 def shape_str(state):
230 shape = BOX_SHAPES.get(state.kind, None)
231 if not shape:
232 return ''
233 return ',shape=%s' % shape
234
235 def __repr__(state):
236 return 'State(name=%r,short_name=%r,out=%d)' % (state.name, state.short_name, len(state.out_edges))
237
238class Fsm:
Neels Hofmeyra568af22017-10-23 04:03:19 +0200239 def __init__(fsm, struct_name, string_name, states_struct_name, from_file=None):
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100240 fsm.states = []
241 fsm.struct_name = struct_name
Neels Hofmeyra568af22017-10-23 04:03:19 +0200242 fsm.string_name = string_name
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100243 fsm.states_struct_name = states_struct_name
244 fsm.from_file = from_file
245 fsm.action_funcs = set()
246 fsm.event_names = set()
Neels Hofmeyra568af22017-10-23 04:03:19 +0200247 fsm.dot_name = fsm.all_names_sanitized()
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100248
249 def parse_states(fsm, src):
250 state = None
251 started = None
252
253 IN_EVENTS = 'events'
254 OUT_STATES = 'states'
255
256 lines = src.splitlines()
257
258 for line in lines:
259 state_name = state_starts(line)
260 if state_name:
261 state = State()
262 fsm.states.append(state)
263 started = None
264 state.name = state_name
265
266 if in_event_starts(line):
267 started = IN_EVENTS
268 if out_state_starts(line):
269 started = OUT_STATES
270
271 if not state or not started:
272 continue
273
274 tokens = states_or_events(line)
275 if started == IN_EVENTS:
276 state.in_event_names.extend(tokens)
277 elif started == OUT_STATES:
278 state.out_state_names.extend(tokens)
279 else:
280 err('ignoring: %r' % tokens)
281
282 a = parse_action(line)
283 if a:
284 state.action = a
285
286
287 for state in fsm.states:
288 if state.action:
289 fsm.action_funcs.add(state.action)
290 if state.in_event_names:
291 fsm.event_names.update(state.in_event_names)
292
293 fsm.make_states_short_names()
294 fsm.ref_out_states()
295
296 def make_states_short_names(fsm):
297 p = common_prefix([s.name for s in fsm.states])
298 for s in fsm.states:
299 s.short_name = s.name[len(p):]
300 return p
301
302 def make_events_short_names(fsm):
303 p = common_prefix(fsm.event_names)
304 for state in fsm.states:
305 for edge in state.out_edges:
306 for event in edge.events:
307 event.short_name = event.name[len(p):]
308
309 def ref_out_states(fsm):
310 for state in fsm.states:
311 for e in [Edge(fsm.find_state_by_name(n, True)) for n in state.out_state_names]:
312 state.add_out_edge(e)
313
314 def find_state_by_name(fsm, name, strict=False):
315 for state in fsm.states:
316 if state.name == name:
317 return state
318 if strict:
319 raise Exception("State not found: %r" % name);
320 return None
321
322 def find_state_by_action(fsm, action):
323 for state in fsm.states:
324 if state.action == action:
325 return state
326 return None
327
328 def add_special_state(fsm, additional_states, name, in_state=None,
329 out_state=None, event_name=None, kind=KIND_FUNC,
330 state_action=None, label=None, edge_action=None):
331 additional_state = None
332 for s in additional_states:
333 if s.short_name == name:
334 additional_state = s
335 break;
336
337 if not additional_state:
338 for s in fsm.states:
339 if s.short_name == name:
340 additional_state = s
341 break;
342
343 if kind == KIND_FUNC and not state_action:
344 state_action = name
345
346 if not additional_state:
347 additional_state = State()
348 additional_state.short_name = name
349 additional_state.action = state_action
350 additional_state.kind = kind
351 additional_state.label = label
352 additional_states.append(additional_state)
353
354 if out_state:
355 additional_state.out_state_names.append(out_state.name)
356 additional_state.add_out_edge(Edge(out_state, event_name, 'dotted',
357 action=edge_action))
358
359 if in_state:
360 in_state.out_state_names.append(additional_state.name)
361 in_state.add_out_edge(Edge(additional_state, event_name, 'dotted',
362 action=edge_action))
363
364
365 def find_event_edges(fsm, c_files):
366 # enrich state transitions between the states with event labels
367 func_to_state_transitions = listdict()
368 for c_file in c_files:
369 func_to_state_transitions.update( c_file.find_state_transitions(fsm.event_names) )
370
371 # edges between explicit states
372 for state in fsm.states:
373 transitions = func_to_state_transitions.get(state.action)
374 if not transitions:
375 continue
376
377 for to_state_name, event_name in transitions:
378 if not event_name:
379 continue
380 for out_edge in state.out_edges:
381 if out_edge.to_state.name == to_state_name:
382 out_edge.add_event_name(event_name)
383
384 additional_states = []
385
386
387 # functions that aren't state actions but still effect state transitions
388 for func_name, transitions in func_to_state_transitions.items():
389 if func_name in fsm.action_funcs:
390 continue
391 for to_state_name, event_name in transitions:
392 to_state = fsm.find_state_by_name(to_state_name)
393 if not to_state:
394 continue
395 fsm.add_special_state(additional_states, func_name, None, to_state, event_name)
396
397
398 event_sources = c_files.find_event_sources(fsm.event_names)
399
400 for state in fsm.states:
401
402 for in_event_name in state.in_event_names:
403 funcs_for_in_event = event_sources.get(in_event_name)
404 if not funcs_for_in_event:
405 continue
406
407 found = False
408 for out_edge in state.out_edges:
409 if out_edge.has_event_name(in_event_name):
410 out_edge.action = r'\n'.join([(f + '()') for f in funcs_for_in_event
411 if f != state.action])
412
413 # if any functions that don't belong to a state trigger events, add
414 # them to the graph as well
415 additional_funcs = [f for f in funcs_for_in_event if f not in fsm.action_funcs]
416 for af in additional_funcs:
417 fsm.add_special_state(additional_states, af, None, state, in_event_name)
418
419 fsm.states.extend(additional_states)
420
421 # do any existing action functions by chance call other action functions?
422 for state in fsm.states:
423 if not state.action:
424 continue
425 callers = c_files.find_callers(state.action)
426 if not callers:
427 continue
428 for other_state in fsm.states:
429 if other_state.action in callers:
430 other_state.add_out_edge(Edge(state, None, 'dotted'))
431
432 def add_fsm_alloc(fsm, c_files):
433
434 allocating_funcs = []
435 for c_file in c_files:
436 allocating_funcs.extend(c_file.fsm_allocators.get(fsm.struct_name, []))
437
438 starting_state = None
439 if fsm.states:
440 # assume the first state starts
441 starting_state = fsm.states[0]
442
443 additional_states = []
444 for func_name in allocating_funcs:
445 fsm.add_special_state(additional_states, func_name, None, starting_state)
446
447 fsm.states.extend(additional_states)
448
449 def add_cross_fsm_links(fsm, fsms, c_files, fsm_meta):
450 for state in fsm.states:
451 if not state.action:
452 continue
453 if state.kind == KIND_FSM:
454 continue
455 callers = c_files.find_callers(state.action)
456
457 if state.kind == KIND_FUNC:
458 callers.append(state.action)
459
460 if not callers:
461 continue
462
463 for caller in callers:
464 for calling_fsm in fsms:
465 if calling_fsm is fsm:
466 continue
467 calling_state = calling_fsm.find_state_by_action(caller)
468 if not calling_state:
469 continue
470 if calling_state.kind == KIND_FSM:
471 continue
472
473 label = None
474 if state.kind == KIND_STATE:
475 label=fsm.struct_name + ': ' + state.short_name
476 edge_action = caller
477 if calling_state.action == edge_action:
478 edge_action = None
Neels Hofmeyra568af22017-10-23 04:03:19 +0200479 calling_fsm.add_special_state(calling_fsm.states, fsm.dot_name,
480 calling_state, kind=KIND_FSM, edge_action=edge_action, label=' '.join(fsm.all_names()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100481
482 label = None
483 if calling_state.kind == KIND_STATE:
484 label=calling_fsm.struct_name + ': ' + calling_state.short_name
485 edge_action = caller
486 if state.action == edge_action:
487 edge_action = None
Neels Hofmeyra568af22017-10-23 04:03:19 +0200488 fsm.add_special_state(fsm.states, calling_fsm.dot_name, None,
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100489 state, kind=KIND_FSM, edge_action=edge_action,
490 label=label)
491
492 # meta overview
Neels Hofmeyra568af22017-10-23 04:03:19 +0200493 meta_called_fsm = fsm_meta.have_state(fsm.dot_name, KIND_FSM)
494 meta_calling_fsm = fsm_meta.have_state(calling_fsm.dot_name, KIND_FSM)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100495 meta_calling_fsm.add_out_edge(Edge(meta_called_fsm))
496
497
498 def have_state(fsm, name, kind=KIND_STATE):
499 state = fsm.find_state_by_name(name)
500 if not state:
501 state = State()
502 state.name = name
503 state.short_name = name
504 state.kind = kind
505 fsm.states.append(state)
506 return state
507
508 def to_dot(fsm):
509 out = ['digraph G {', 'rankdir=LR;']
510
511 for state in fsm.states:
512 out.append('%s [label="%s"%s]' % (state.short_name, state.get_label(),
513 state.shape_str()))
514
515 for state in fsm.states:
516 for out_edge in state.out_edges:
517 attrs = []
518 labels = []
519 if out_edge.events:
520 labels.extend(out_edge.event_labels())
521 if out_edge.actions:
522 labels.extend(out_edge.action_labels())
523 if labels:
524 attrs.append('label="%s"' % (r'\n'.join(labels)))
525 if out_edge.style:
526 attrs.append('style=%s'% out_edge.style)
527 attrs_str = ''
528 if attrs:
529 attrs_str = ' [%s]' % (','.join(attrs))
530 out.append('%s->%s%s' % (state.short_name, out_edge.to_state.short_name, attrs_str))
531
532 out.append('}\n')
533
534 return '\n'.join(out)
535
Neels Hofmeyra568af22017-10-23 04:03:19 +0200536 def all_names(fsm):
537 n = []
538 if fsm.from_file:
539 n.append(os.path.basename(fsm.from_file.path))
540 if fsm.struct_name:
541 n.append(fsm.struct_name)
542 if fsm.string_name:
543 n.append(fsm.string_name)
544 return n
545
546 def all_names_sanitized(fsm, sep='_'):
547 n = sep.join(fsm.all_names())
548 n = re_insane_dot_name_chars.sub('_', n)
549 return n
550
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100551 def write_dot_file(fsm):
Neels Hofmeyra568af22017-10-23 04:03:19 +0200552 dot_path = '%s.dot' % ('_'.join(fsm.all_names()))
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100553 f = open(dot_path, 'w')
554 f.write(fsm.to_dot())
555 f.close()
556 print(dot_path)
557
558
559re_fsm = re.compile(r'struct osmo_fsm ([a-z_][a-z_0-9]*) =')
Neels Hofmeyra568af22017-10-23 04:03:19 +0200560re_fsm_string_name = re.compile(r'\bname = "([^"]*)"')
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100561re_fsm_states_struct_name = re.compile(r'\bstates = ([a-z_][a-z_0-9]*)\W*,')
562re_fsm_states = re.compile(r'struct osmo_fsm_state ([a-z_][a-z_0-9]*)\[\] =')
563re_func = re.compile(r'(\b[a-z_][a-z_0-9]*\b)\([^)]*\)\W*^{', re.MULTILINE)
564re_state_trigger = re.compile(r'osmo_fsm_inst_state_chg\([^,]+,\W*([A-Z_][A-Z_0-9]*)\W*,', re.M)
565re_fsm_alloc = re.compile(r'osmo_fsm_inst_alloc[_child]*\(\W*&([a-z_][a-z_0-9]*),', re.M)
566re_fsm_event_dispatch = re.compile(r'osmo_fsm_inst_dispatch\(\W*[^,]+,\W*([A-Z_][A-Z_0-9]*)\W*,', re.M)
567
568class CFile():
569 def __init__(c_file, path):
570 c_file.path = path
571 c_file.src = open(path).read()
572 c_file.funcs = {}
573 c_file.fsm_allocators = listdict()
574
575 def extract_block(c_file, brace_open, brace_close, start):
576 pos = 0
577 try:
578 src = c_file.src
579 block_start = src.find(brace_open, start)
580
581 pos = block_start
582 level = 1
583 while level > 0:
584 pos += 1
585 if src[pos] == brace_open:
586 level += 1
587 elif src[pos] == brace_close:
588 level -= 1
589
590 return src[block_start+1:pos]
591 except:
592 print("Error while trying to extract a code block from %r char pos %d" % (c_file.path, pos))
593 print("Block start at char pos %d" % block_start)
594 try:
595 print(src[block_start - 20 : block_start + 20])
596 print('...')
597 print(src[pos - 20 : pos + 20])
598 except:
599 pass
600 return ''
601
602
603 def find_fsms(c_file):
604 fsms = []
605 for m in re_fsm.finditer(c_file.src):
606 struct_name = m.group(1)
607 struct_def = c_file.extract_block('{', '}', m.start())
Neels Hofmeyra568af22017-10-23 04:03:19 +0200608 string_name = (re_fsm_string_name.findall(struct_def) or [None])[0]
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100609 states_struct_name = re_fsm_states_struct_name.findall(struct_def)[0]
Neels Hofmeyra568af22017-10-23 04:03:19 +0200610 fsm = Fsm(struct_name, string_name, states_struct_name, c_file)
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100611 fsms.append(fsm)
612 return fsms
613
614 def find_fsm_states(c_file, fsms):
615 for m in re_fsm_states.finditer(c_file.src):
616 states_struct_name = m.group(1)
617 for fsm in fsms:
618 if states_struct_name == fsm.states_struct_name:
619 fsm.parse_states(c_file.extract_block('{', '}', m.start()))
620
621 def parse_functions(c_file):
622 funcs = {}
623 for m in re_func.finditer(c_file.src):
624 name = m.group(1)
625 func_src = c_file.extract_block('{', '}', m.start())
626 funcs[name] = func_src
627 c_file.funcs = funcs
628 c_file.find_fsm_allocators()
629
630 def find_callers(c_file, func_name):
631 func_call = func_name + '('
632 callers = []
633 for func_name, src in c_file.funcs.items():
634 if src.find(func_call) >= 0:
635 callers.append(func_name)
636 return callers
637
638 def find_fsm_allocators(c_file):
639 c_file.fsm_allocators = listdict()
640 for func_name, src in c_file.funcs.items():
641 for m in re_fsm_alloc.finditer(src):
642 fsm_struct_name = m.group(1)
643 c_file.fsm_allocators.add(fsm_struct_name, func_name)
644
645 def find_state_transitions(c_file, event_names):
646 TO_STATE = 'TO_STATE'
647 EVENT = 'EVENT'
648 func_to_state_transitions = listdict()
649
650 for func_name, src in c_file.funcs.items():
651 found_tokens = []
652
653 for m in re_state_trigger.finditer(src):
654 to_state = m.group(1)
655 found_tokens.append((m.start(), TO_STATE, to_state))
656
657 for event in event_names:
658 re_event = re.compile(r'\b(' + event + r')\b')
659 for m in re_event.finditer(src):
660 event = m.group(1)
661 found_tokens.append((m.start(), EVENT, event))
662
663 found_tokens = sorted(found_tokens)
664
665 last_event = None
666 for start, kind, name in found_tokens:
667 if kind == EVENT:
668 last_event = name
669 else:
670 func_to_state_transitions.add(func_name, (name, last_event))
671
672 return func_to_state_transitions
673
674
675 def find_event_sources(c_file, event_names):
676 c_file.event_sources = listdict()
677 for func_name, src in c_file.funcs.items():
678 for m in re_fsm_event_dispatch.finditer(src):
679 event_name = m.group(1)
680 c_file.event_sources.add(event_name, func_name)
681
682class CFiles(list):
683
684 def find_callers(c_files, func_name):
685 callers = []
686 for c_file in c_files:
687 callers.extend(c_file.find_callers(func_name))
688 return callers
689
690 def find_func_to_state_transitions(c_files):
691 func_to_state_transitions = listdict()
692 for c_file in c_files:
693 func_to_state_transitions.update( c_file.find_state_transitions(fsm.event_names) )
694 return func_to_state_transitions
695
696 def find_event_sources(c_files, event_names):
697 event_sources = listdict()
698 for c_file in c_files:
699 for event, sources in c_file.event_sources.items():
700 if event in event_names:
701 event_sources.extend(event, sources)
702 return event_sources
703
704c_files = CFiles()
705paths_seen = set()
706for path in sys.argv[1:]:
707 if path in paths_seen:
708 continue
709 paths_seen.add(path)
710 c_file = CFile(path)
711 c_files.append(c_file)
712
713for c_file in c_files:
714 c_file.parse_functions()
715
716fsms = []
717for c_file in c_files:
718 fsms.extend(c_file.find_fsms())
719
720for c_file in c_files:
721 c_file.find_fsm_states(fsms)
722 c_file.find_event_sources(fsms)
723
724for fsm in fsms:
725 fsm.find_event_edges(c_files)
726 fsm.add_fsm_alloc(c_files)
727
Neels Hofmeyra568af22017-10-23 04:03:19 +0200728fsm_meta = Fsm("meta", None, "meta")
Neels Hofmeyr0898a002016-11-16 14:36:29 +0100729for fsm in fsms:
730 fsm.add_cross_fsm_links(fsms, c_files, fsm_meta)
731
732for fsm in fsms:
733 fsm.make_events_short_names()
734
735for fsm in fsms:
736 fsm.write_dot_file()
737
738fsm_meta.write_dot_file()
739
740
741# vim: tabstop=2 shiftwidth=2 expandtab