// 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 . 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