github/workflows/pycopy-test: Upgrade Pycopy to 3.6.1.
[ScratchABlock.git] / funcdb_dot.py
blob2a43f12d8258f47ec8983ff33c2d8fc47ce81f47
1 #!/usr/bin/env python3
2 import sys
3 import argparse
5 import yaml
7 from utils import maybesorted
10 argp = argparse.ArgumentParser(description="Render function database as various graphs")
11 argp.add_argument("file", help="Input file (YAML)")
12 argp.add_argument("-o", "--output", help="Output file (default stdout)")
13 argp.add_argument("--func", help="Start from this function")
14 argp.add_argument("--no-refs", action="store_true", help="Show only direct calls, not refs to other functions "
15 "(otherwise refs shown as dashed lines)")
16 argp.add_argument("--group", action="append", default=[], help="Group some functions together, GROUP is name=file.txt, "
17 "if name is '_ignore_', don't graph these functions")
18 argp.add_argument("--each-call", action="store_true", help="Show multiple edges for each call site")
19 args = argp.parse_args()
21 IGNORE = set()
22 GROUPS = {}
25 def read_func_list(fname):
26 res = []
27 with open(fname) as f:
28 for l in f:
29 l = l.strip()
30 if not l or l[0] == "#":
31 continue
32 res.append(l)
33 return res
36 for g in args.group:
37 name, fname = g.split("=", 1)
38 funcs = read_func_list(fname)
39 if name == "_ignore_":
40 IGNORE.update(funcs)
41 else:
42 GROUPS[name] = set(funcs)
45 FUNC_DB = yaml.load(open(args.file))
47 if args.output:
48 out = open(args.output, "w")
49 else:
50 out = sys.stdout
53 dup_set = set()
56 def index_by_name(db):
57 for addr, props in list(db.items()):
58 db[props["label"]] = props
61 def get_group(funcname):
62 if funcname in IGNORE:
63 return "_ignore_"
64 for name, funcs in GROUPS.items():
65 if funcname in funcs:
66 return name
69 def map_group(funcname):
70 return get_group(funcname) or funcname
73 def dump_level(func, props):
74 if get_group(props["label"]):
75 return
76 for propname in ("calls", "func_refs"):
77 if propname == "func_refs" and args.no_refs:
78 continue
79 for callee in maybesorted(props.get(propname, [])):
80 if callee in IGNORE:
81 continue
82 callee = map_group(callee)
84 if not args.each_call:
85 if (props["label"], callee) in dup_set:
86 continue
87 dup_set.add((props["label"], callee))
88 attrs = {"func_refs": " [style=dashed]"}.get(propname, "")
89 out.write("%s -> %s%s\n" % (props["label"], callee, attrs))
92 out.write("digraph G {\n")
94 if args.func:
95 index_by_name(FUNC_DB)
96 todo = [args.func]
97 done = set()
98 while todo:
99 f = todo.pop()
100 props = FUNC_DB.get(f)
101 if not props:
102 continue
103 dump_level(f, props)
104 done.add(f)
105 for callee in props["calls"]:
106 if callee not in done:
107 todo.append(callee)
108 else:
109 for func, props in maybesorted(FUNC_DB.items()):
110 dump_level(func, props)
112 out.write("}\n")