use crate::node;
use crate::node::{NodeKind, NormalizedNode};
use std::mem;
#[must_use]
pub fn similarity_score(a: &NormalizedNode, b: &NormalizedNode) -> f64 {
let nodes_a = node::count_nodes(a);
let nodes_b = node::count_nodes(b);
if nodes_a == 0 && nodes_b == 0 {
return 1.0;
}
let matching = count_matching(a, b);
(2.0 * matching as f64) / (nodes_a + nodes_b) as f64
}
fn count_matching(a: &NormalizedNode, b: &NormalizedNode) -> usize {
if a.is_none() || b.is_none() {
return 0;
}
if mem::discriminant(&a.kind) != mem::discriminant(&b.kind) {
return 0;
}
if let (NodeKind::MacroCall { name: na }, NodeKind::MacroCall { name: nb }) = (&a.kind, &b.kind)
&& na != nb
{
return 0;
}
let self_match = usize::from(a.kind == b.kind);
let children = if is_sequence(&a.kind) {
aligned_children(&a.children, &b.children)
} else {
slotted_children(&a.children, &b.children)
};
self_match + children
}
const fn is_sequence(kind: &NodeKind) -> bool {
matches!(kind, NodeKind::Block | NodeKind::Tuple | NodeKind::Array)
}
fn slotted_children(a: &[NormalizedNode], b: &[NormalizedNode]) -> usize {
a.iter()
.zip(b)
.map(|(child_a, child_b)| count_matching(child_a, child_b))
.sum()
}
fn aligned_children(a: &[NormalizedNode], b: &[NormalizedNode]) -> usize {
if a.is_empty() || b.is_empty() {
return 0;
}
let mut best = vec![vec![0usize; b.len() + 1]; a.len() + 1];
for i in 1..=a.len() {
for j in 1..=b.len() {
let paired = best[i - 1][j - 1] + count_matching(&a[i - 1], &b[j - 1]);
best[i][j] = paired.max(best[i - 1][j]).max(best[i][j - 1]);
}
}
best[a.len()][b.len()]
}