use super::{BUILTIN, FieldDef};
use crate::attrs::num_attr;
use crate::registry::{Engine, MetricDef, references};
use code_ranker_plugin_api::attrs::AttrValue;
use code_ranker_plugin_api::metrics::MetricInputs;
use code_ranker_plugin_api::node::Node;
use std::collections::BTreeMap;
use std::sync::LazyLock;
const GRAPH_KEYS: &[&str] = &["fan_in", "fan_out", "fan_out_external", "cycle"];
fn is_graph_derived(formula: &str) -> bool {
GRAPH_KEYS.iter().any(|k| references(formula, k))
}
fn build_engine(keep: impl Fn(&str) -> bool) -> (BTreeMap<String, MetricDef>, Engine) {
let defs: BTreeMap<String, MetricDef> = BUILTIN
.fields
.iter()
.filter_map(|(k, f): (&String, &FieldDef)| {
f.formula_cel
.as_ref()
.filter(|cel| keep(cel))
.map(|cel| (k.clone(), derived_def(cel, f.omit_at)))
})
.collect();
let engine = Engine::compile(&defs).expect("metrics/builtin.toml fields compile");
(defs, engine)
}
pub(crate) static DERIVED: LazyLock<(BTreeMap<String, MetricDef>, Engine)> =
LazyLock::new(|| build_engine(|cel| !is_graph_derived(cel)));
static GRAPH_DERIVED: LazyLock<(BTreeMap<String, MetricDef>, Engine)> =
LazyLock::new(|| build_engine(is_graph_derived));
fn derived_def(cel: &str, omit_at: f64) -> MetricDef {
MetricDef {
formula_cel: cel.to_string(),
value_type: "float".to_string(),
omit_at,
..MetricDef::default()
}
}
pub fn write_metrics(node: &mut Node, i: &MetricInputs) {
{
let mut put = |key: &str, v: f64| {
let a = num_attr(v);
if a == num_attr(0.0) {
node.attrs.remove(key);
} else {
node.attrs.insert(key.to_string(), a);
}
};
put("cognitive", i.cognitive);
put("exits", i.exits);
put("args", i.args);
put("closures", i.closures);
put("eta1", i.eta1);
put("eta2", i.eta2);
put("n1", i.n1);
put("n2", i.n2);
put("spaces", i.spaces);
put("branches", i.branches);
put("span_sloc", i.span_sloc);
if i.sloc > 0.0 {
put("sloc", i.sloc);
put("lloc", i.lloc);
put("cloc", i.cloc);
put("blank", i.blank);
}
put("tloc", i.tloc);
}
let (defs, engine) = &*DERIVED;
for (key, value) in engine.eval_node(&inputs_map(i), &BTreeMap::new()) {
let omit = defs.get(&key).map(|d| d.omit_at).unwrap_or(0.0);
let a = num_attr(value);
if a == num_attr(omit) {
node.attrs.remove(&key);
} else {
node.attrs.insert(key, a);
}
}
}
fn inputs_map(i: &MetricInputs) -> BTreeMap<String, f64> {
BTreeMap::from([
("eta1".to_string(), i.eta1),
("eta2".to_string(), i.eta2),
("n1".to_string(), i.n1),
("n2".to_string(), i.n2),
("spaces".to_string(), i.spaces),
("branches".to_string(), i.branches),
("cognitive".to_string(), i.cognitive),
("exits".to_string(), i.exits),
("args".to_string(), i.args),
("closures".to_string(), i.closures),
("sloc".to_string(), i.sloc),
("lloc".to_string(), i.lloc),
("cloc".to_string(), i.cloc),
("blank".to_string(), i.blank),
("tloc".to_string(), i.tloc),
("span_sloc".to_string(), i.span_sloc),
])
}
const GRAPH_DERIVED_SEED: &[&str] = &[
"eta1",
"eta2",
"n1",
"n2",
"spaces",
"branches",
"cognitive",
"exits",
"args",
"closures",
"sloc",
"lloc",
"cloc",
"blank",
"tloc",
"span_sloc",
"fan_in",
"fan_out",
"fan_out_external",
];
pub fn write_derived(node: &mut Node) {
let (defs, engine) = &*GRAPH_DERIVED;
if defs.is_empty() {
return;
}
let mut attrs: BTreeMap<String, f64> = GRAPH_DERIVED_SEED
.iter()
.map(|k| ((*k).to_string(), 0.0))
.collect();
for (k, v) in &node.attrs {
match v {
AttrValue::Int(i) => {
attrs.insert(k.clone(), *i as f64);
}
AttrValue::Float(f) => {
attrs.insert(k.clone(), *f);
}
AttrValue::Str(_) | AttrValue::Bool(_) => {}
}
}
for (key, value) in engine.eval_node(&attrs, &BTreeMap::new()) {
let omit = defs.get(&key).map(|d| d.omit_at).unwrap_or(0.0);
let a = num_attr(value);
if a == num_attr(omit) {
node.attrs.remove(&key);
} else {
node.attrs.insert(key, a);
}
}
}