| 1 | module callgraph |
| 2 | |
| 3 | import v.ast |
| 4 | import v.ast.walker |
| 5 | import v.pref |
| 6 | import v.dotgraph |
| 7 | |
| 8 | // callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls |
| 9 | // that function make transitively |
| 10 | pub fn show(mut table ast.Table, pref_ &pref.Preferences, ast_files []&ast.File) { |
| 11 | mut mapper := &Mapper{ |
| 12 | pref: pref_ |
| 13 | table: table |
| 14 | dg: dotgraph.new('CallGraph', 'CallGraph for ${pref_.path}', 'green') |
| 15 | } |
| 16 | // Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"]; |
| 17 | // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; |
| 18 | for afile in ast_files { |
| 19 | walker.walk(mut mapper, afile) |
| 20 | } |
| 21 | mapper.dg.finish() |
| 22 | } |
| 23 | |
| 24 | @[heap] |
| 25 | struct Mapper { |
| 26 | pos int |
| 27 | mut: |
| 28 | pref &pref.Preferences = unsafe { nil } |
| 29 | table &ast.Table = unsafe { nil } |
| 30 | file &ast.File = unsafe { nil } |
| 31 | node &ast.Node = unsafe { nil } |
| 32 | fn_decl &ast.FnDecl = unsafe { nil } |
| 33 | caller_name string |
| 34 | dot_caller_name string |
| 35 | is_caller_used bool |
| 36 | dg dotgraph.DotGraph |
| 37 | } |
| 38 | |
| 39 | fn (mut m Mapper) dot_normalise_node_name(name string) string { |
| 40 | res := name.replace_each([ |
| 41 | '.', |
| 42 | '_', |
| 43 | '==', |
| 44 | 'op_eq', |
| 45 | '>=', |
| 46 | 'op_greater_eq', |
| 47 | '<=', |
| 48 | 'op_lesser_eq', |
| 49 | '>', |
| 50 | 'op_greater', |
| 51 | '<', |
| 52 | 'op_lesser', |
| 53 | '+', |
| 54 | 'op_plus', |
| 55 | '-', |
| 56 | 'op_minus', |
| 57 | '/', |
| 58 | 'op_divide', |
| 59 | '*', |
| 60 | 'op_multiply', |
| 61 | '^', |
| 62 | 'op_xor', |
| 63 | '|', |
| 64 | 'op_or', |
| 65 | '&', |
| 66 | 'op_and', |
| 67 | ]) |
| 68 | return res |
| 69 | } |
| 70 | |
| 71 | fn (mut m Mapper) fn_name(fname string, receiver_type ast.Type, is_method bool) string { |
| 72 | if !is_method { |
| 73 | return fname |
| 74 | } |
| 75 | rec_sym := m.table.sym(receiver_type) |
| 76 | return '${rec_sym.name}.${fname}' |
| 77 | } |
| 78 | |
| 79 | fn (mut m Mapper) dot_fn_name(fname string, recv_type ast.Type, is_method bool) string { |
| 80 | if is_method { |
| 81 | return 'Node_method_' + int(recv_type).str() + '_' + m.dot_normalise_node_name(fname) |
| 82 | } |
| 83 | return 'Node_fn_' + m.dot_normalise_node_name(fname) |
| 84 | } |
| 85 | |
| 86 | fn (mut m Mapper) visit(node &ast.Node) ! { |
| 87 | m.node = unsafe { node } |
| 88 | match node { |
| 89 | ast.File { |
| 90 | m.file = unsafe { &node } |
| 91 | } |
| 92 | ast.Stmt { |
| 93 | match node { |
| 94 | ast.FnDecl { |
| 95 | m.is_caller_used = true |
| 96 | if m.pref.skip_unused { |
| 97 | m.is_caller_used = m.table.used_features.used_fns[node.fkey()] |
| 98 | } |
| 99 | m.fn_decl = unsafe { &node } |
| 100 | m.caller_name = m.fn_name(node.name, node.receiver.typ, node.is_method) |
| 101 | m.dot_caller_name = m.dot_fn_name(node.name, node.receiver.typ, node.is_method) |
| 102 | if m.is_caller_used { |
| 103 | m.dg.new_node(m.caller_name, |
| 104 | node_name: m.dot_caller_name |
| 105 | should_highlight: m.caller_name == 'main.main' |
| 106 | ) |
| 107 | } |
| 108 | } |
| 109 | else {} |
| 110 | } |
| 111 | } |
| 112 | ast.Expr { |
| 113 | match node { |
| 114 | ast.CallExpr { |
| 115 | if m.is_caller_used { |
| 116 | dot_called_name := m.dot_fn_name(node.name, node.receiver_type, |
| 117 | node.is_method) |
| 118 | // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; |
| 119 | m.dg.new_edge(m.dot_caller_name, dot_called_name, |
| 120 | should_highlight: m.caller_name == 'main.main' |
| 121 | ) |
| 122 | } |
| 123 | } |
| 124 | else {} |
| 125 | } |
| 126 | } |
| 127 | else {} |
| 128 | } |
| 129 | } |
| 130 | |