Files
cell/analyze.cm

145 lines
3.6 KiB
Plaintext

// analyze.cm — Static analysis over index data.
//
// All functions take an index object (from index.cm) and return structured results.
// Does not depend on streamline — operates purely on source-semantic data.
var analyze = {}
// Find all references to a name, with optional scope filter.
// scope: "top" (enclosing == null), "fn" (enclosing != null), null (all)
analyze.find_refs = function(idx, name, scope) {
var hits = []
var i = 0
var ref = null
while (i < length(idx.references)) {
ref = idx.references[i]
if (ref.name == name) {
if (scope == null) {
hits[] = ref
} else if (scope == "top" && ref.enclosing == null) {
hits[] = ref
} else if (scope == "fn" && ref.enclosing != null) {
hits[] = ref
}
}
i = i + 1
}
return hits
}
// Find all <name>.<property> usage patterns (channel analysis).
// Only counts unshadowed uses (name not declared as local var in scope).
analyze.channels = function(idx, name) {
var channels = {}
var summary = {}
var i = 0
var cs = null
var callee = null
var prop = null
var prefix_dot = name + "."
while (i < length(idx.call_sites)) {
cs = idx.call_sites[i]
callee = cs.callee
if (callee != null && starts_with(callee, prefix_dot)) {
prop = text(callee, length(prefix_dot), length(callee))
if (channels[prop] == null) {
channels[prop] = []
}
channels[prop][] = {span: cs.span}
if (summary[prop] == null) {
summary[prop] = 0
}
summary[prop] = summary[prop] + 1
}
i = i + 1
}
return {channels: channels, summary: summary}
}
// Find declarations by name, with optional kind filter.
// kind: "var", "def", "fn", "param", or null (any)
analyze.find_decls = function(idx, name, kind) {
var hits = []
var i = 0
var sym = null
while (i < length(idx.symbols)) {
sym = idx.symbols[i]
if (sym.name == name) {
if (kind == null || sym.kind == kind) {
hits[] = sym
}
}
i = i + 1
}
return hits
}
// Find intrinsic usage by name.
analyze.find_intrinsic = function(idx, name) {
var hits = []
var i = 0
var ref = null
if (idx.intrinsic_refs == null) return hits
while (i < length(idx.intrinsic_refs)) {
ref = idx.intrinsic_refs[i]
if (ref.name == name) {
hits[] = ref
}
i = i + 1
}
return hits
}
// Call sites with >4 args — always a compile error (max arity is 4).
analyze.excess_args = function(idx) {
var hits = []
var i = 0
var cs = null
while (i < length(idx.call_sites)) {
cs = idx.call_sites[i]
if (cs.args_count > 4) {
hits[] = {span: cs.span, callee: cs.callee, args_count: cs.args_count}
}
i = i + 1
}
return hits
}
// Extract module export shape from index data (for cross-module analysis).
analyze.module_summary = function(idx) {
var exports = {}
var i = 0
var j = 0
var exp = null
var sym = null
var found = false
if (idx.exports == null) return {exports: exports}
while (i < length(idx.exports)) {
exp = idx.exports[i]
found = false
if (exp.symbol_id != null) {
j = 0
while (j < length(idx.symbols)) {
sym = idx.symbols[j]
if (sym.symbol_id == exp.symbol_id) {
if (sym.kind == "fn" && sym.params != null) {
exports[exp.name] = {type: "function", arity: length(sym.params)}
} else {
exports[exp.name] = {type: sym.kind}
}
found = true
break
}
j = j + 1
}
}
if (!found) {
exports[exp.name] = {type: "unknown"}
}
i = i + 1
}
return {exports: exports}
}
return analyze