// xref.ce — cross-reference / call graph // // Usage: // cell xref Full creation tree // cell xref --callers Who creates function [N]? // cell xref --callees What does [N] create/call? // cell xref --optimized Use optimized IR // cell xref --dot DOT graph for graphviz var shop = use("internal/shop") var run = function() { var filename = null var use_optimized = false var show_callers = null var show_callees = null var show_dot = false var i = 0 var compiled = null var creates = {} var created_by = {} var func_names = {} var fi = 0 var func = null var fname = null var main_name = null var creators = null var c = null var line_info = null var children = null var ch = null var ch_line = null var parent_keys = null var ki = 0 var parent_idx = 0 var ch_list = null var ci = 0 var printed = {} while (i < length(args)) { if (args[i] == '--callers') { i = i + 1 show_callers = number(args[i]) } else if (args[i] == '--callees') { i = i + 1 show_callees = number(args[i]) } else if (args[i] == '--dot') { show_dot = true } else if (args[i] == '--optimized') { use_optimized = true } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell xref [--callers ] [--callees ] [--dot] [--optimized] ") log.console("") log.console(" --callers Who creates function [N]?") log.console(" --callees What does [N] create/call?") log.console(" --dot Output DOT format for graphviz") log.console(" --optimized Use optimized IR") return null } else if (!starts_with(args[i], '-')) { filename = args[i] } i = i + 1 } if (!filename) { log.console("Usage: cell xref [--callers ] [--callees ] [--dot] [--optimized] ") return null } if (use_optimized) { compiled = shop.compile_file(filename) } else { compiled = shop.mcode_file(filename) } main_name = compiled.name != null ? compiled.name : "
" func_names["-1"] = main_name var scan_func = function(func, parent_idx) { var instrs = func.instructions var j = 0 var instr = null var n = 0 var child_idx = null var instr_line = null if (instrs == null) return null while (j < length(instrs)) { instr = instrs[j] if (is_array(instr) && instr[0] == "function") { n = length(instr) child_idx = instr[2] instr_line = instr[n - 2] if (!creates[text(parent_idx)]) { creates[text(parent_idx)] = [] } push(creates[text(parent_idx)], {child: child_idx, line: instr_line}) if (!created_by[text(child_idx)]) { created_by[text(child_idx)] = [] } push(created_by[text(child_idx)], {parent: parent_idx, line: instr_line}) } j = j + 1 } return null } if (compiled.main != null) { scan_func(compiled.main, -1) } if (compiled.functions != null) { fi = 0 while (fi < length(compiled.functions)) { func = compiled.functions[fi] fname = func.name != null ? func.name : "" func_names[text(fi)] = fname scan_func(func, fi) fi = fi + 1 } } var func_label = function(idx) { var name = func_names[text(idx)] if (idx == -1) return main_name if (name != null) return `[${text(idx)}] ${name}` return `[${text(idx)}]` } var safe_label = function(idx) { var name = func_names[text(idx)] if (name != null) return replace(name, '"', '\\"') if (idx == -1) return main_name return `func_${text(idx)}` } var node_id = function(idx) { if (idx == -1) return "main" return `f${text(idx)}` } // --callers mode if (show_callers != null) { creators = created_by[text(show_callers)] log.compile(`\nCallers of ${func_label(show_callers)}:`) if (creators == null || length(creators) == 0) { log.compile(" (none - may be main or unreferenced)") } else { i = 0 while (i < length(creators)) { c = creators[i] line_info = c.line != null ? ` at line ${text(c.line)}` : "" log.compile(` ${func_label(c.parent)}${line_info}`) i = i + 1 } } return null } // --callees mode if (show_callees != null) { children = creates[text(show_callees)] log.compile(`\nCallees of ${func_label(show_callees)}:`) if (children == null || length(children) == 0) { log.compile(" (none)") } else { i = 0 while (i < length(children)) { ch = children[i] ch_line = ch.line != null ? ` at line ${text(ch.line)}` : "" log.compile(` ${func_label(ch.child)}${ch_line}`) i = i + 1 } } return null } // --dot mode if (show_dot) { log.compile("digraph xref {") log.compile(" rankdir=TB;") log.compile(" node [shape=box, style=filled, fillcolor=lightyellow];") log.compile(` ${node_id(-1)} [label="${safe_label(-1)}"];`) if (compiled.functions != null) { fi = 0 while (fi < length(compiled.functions)) { log.compile(` ${node_id(fi)} [label="${safe_label(fi)}"];`) fi = fi + 1 } } parent_keys = array(creates) ki = 0 while (ki < length(parent_keys)) { parent_idx = number(parent_keys[ki]) ch_list = creates[parent_keys[ki]] ci = 0 while (ci < length(ch_list)) { log.compile(` ${node_id(parent_idx)} -> ${node_id(ch_list[ci].child)};`) ci = ci + 1 } ki = ki + 1 } log.compile("}") return null } // Default: indented tree from main var print_tree = function(idx, depth) { var indent = "" var d = 0 var children = null var ci = 0 var child = null while (d < depth) { indent = indent + " " d = d + 1 } log.compile(`${indent}${func_label(idx)}`) if (printed[text(idx)]) { log.compile(`${indent} (already shown)`) return null } printed[text(idx)] = true children = creates[text(idx)] if (children != null) { ci = 0 while (ci < length(children)) { child = children[ci] print_tree(child.child, depth + 1) ci = ci + 1 } } return null } log.compile("") print_tree(-1, 0) return null } run() $stop()