module callgraph import v.ast import v.ast.walker import v.pref import v.dotgraph // callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls // that function make transitively pub fn show(mut table ast.Table, pref_ &pref.Preferences, ast_files []&ast.File) { mut mapper := &Mapper{ pref: pref_ table: table dg: dotgraph.new('CallGraph', 'CallGraph for ${pref_.path}', 'green') } // Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"]; // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; for afile in ast_files { walker.walk(mut mapper, afile) } mapper.dg.finish() } @[heap] struct Mapper { pos int mut: pref &pref.Preferences = unsafe { nil } table &ast.Table = unsafe { nil } file &ast.File = unsafe { nil } node &ast.Node = unsafe { nil } fn_decl &ast.FnDecl = unsafe { nil } caller_name string dot_caller_name string is_caller_used bool dg dotgraph.DotGraph } fn (mut m Mapper) dot_normalise_node_name(name string) string { res := name.replace_each([ '.', '_', '==', 'op_eq', '>=', 'op_greater_eq', '<=', 'op_lesser_eq', '>', 'op_greater', '<', 'op_lesser', '+', 'op_plus', '-', 'op_minus', '/', 'op_divide', '*', 'op_multiply', '^', 'op_xor', '|', 'op_or', '&', 'op_and', ]) return res } fn (mut m Mapper) fn_name(fname string, receiver_type ast.Type, is_method bool) string { if !is_method { return fname } rec_sym := m.table.sym(receiver_type) return '${rec_sym.name}.${fname}' } fn (mut m Mapper) dot_fn_name(fname string, recv_type ast.Type, is_method bool) string { if is_method { return 'Node_method_' + int(recv_type).str() + '_' + m.dot_normalise_node_name(fname) } return 'Node_fn_' + m.dot_normalise_node_name(fname) } fn (mut m Mapper) visit(node &ast.Node) ! { m.node = unsafe { node } match node { ast.File { m.file = unsafe { &node } } ast.Stmt { match node { ast.FnDecl { m.is_caller_used = true if m.pref.skip_unused { m.is_caller_used = m.table.used_features.used_fns[node.fkey()] } m.fn_decl = unsafe { &node } m.caller_name = m.fn_name(node.name, node.receiver.typ, node.is_method) m.dot_caller_name = m.dot_fn_name(node.name, node.receiver.typ, node.is_method) if m.is_caller_used { m.dg.new_node(m.caller_name, node_name: m.dot_caller_name should_highlight: m.caller_name == 'main.main' ) } } else {} } } ast.Expr { match node { ast.CallExpr { if m.is_caller_used { dot_called_name := m.dot_fn_name(node.name, node.receiver_type, node.is_method) // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; m.dg.new_edge(m.dot_caller_name, dot_called_name, should_highlight: m.caller_name == 'main.main' ) } } else {} } } else {} } }