250 lines
6.3 KiB
Plaintext
250 lines
6.3 KiB
Plaintext
// xref.ce — cross-reference / call graph
|
|
//
|
|
// Usage:
|
|
// cell xref <file> Full creation tree
|
|
// cell xref --callers <N> <file> Who creates function [N]?
|
|
// cell xref --callees <N> <file> What does [N] create/call?
|
|
// cell xref --optimized <file> Use optimized IR
|
|
// cell xref --dot <file> 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 <N>] [--callees <N>] [--dot] [--optimized] <file>")
|
|
log.console("")
|
|
log.console(" --callers <N> Who creates function [N]?")
|
|
log.console(" --callees <N> 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 <N>] [--callees <N>] [--dot] [--optimized] <file>")
|
|
return null
|
|
}
|
|
|
|
if (use_optimized) {
|
|
compiled = shop.compile_file(filename)
|
|
} else {
|
|
compiled = shop.mcode_file(filename)
|
|
}
|
|
|
|
main_name = compiled.name != null ? compiled.name : "<main>"
|
|
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)] = []
|
|
}
|
|
creates[text(parent_idx)][] = {child: child_idx, line: instr_line}
|
|
if (!created_by[text(child_idx)]) {
|
|
created_by[text(child_idx)] = []
|
|
}
|
|
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 : "<anonymous>"
|
|
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()
|