Files
cell/xref.ce
2026-02-21 01:21:53 -06:00

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)] = []
}
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 : "<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()