Skip to main content

agentic_evolve_core/matching/
signature.rs

1//! SignatureMatcher — matches function signatures to patterns.
2
3use crate::types::error::EvolveResult;
4use crate::types::match_result::{MatchContext, MatchResult, MatchScore};
5use crate::types::pattern::{FunctionSignature, Pattern};
6
7/// Matches patterns based on function signature similarity.
8#[derive(Debug, Default)]
9pub struct SignatureMatcher;
10
11impl SignatureMatcher {
12    pub fn new() -> Self {
13        Self
14    }
15
16    pub fn find_matches(
17        &self,
18        signature: &FunctionSignature,
19        patterns: &[&Pattern],
20        _context: &MatchContext,
21        limit: usize,
22    ) -> EvolveResult<Vec<MatchResult>> {
23        let mut results: Vec<MatchResult> = patterns
24            .iter()
25            .map(|p| {
26                let score = self.score_match(p, signature);
27                MatchResult {
28                    pattern_id: p.id.clone(),
29                    pattern: (*p).clone(),
30                    score: MatchScore::from_single(score),
31                    suggested_bindings: std::collections::HashMap::new(),
32                }
33            })
34            .filter(|r| r.score.combined > 0.0)
35            .collect();
36
37        results.sort_by(|a, b| {
38            b.score
39                .combined
40                .partial_cmp(&a.score.combined)
41                .unwrap_or(std::cmp::Ordering::Equal)
42        });
43        results.truncate(limit);
44        Ok(results)
45    }
46
47    pub fn score_match(&self, pattern: &Pattern, signature: &FunctionSignature) -> f64 {
48        let mut score = 0.0;
49        let mut factors = 0;
50
51        // Name similarity
52        let name_score = string_similarity(&pattern.signature.name, &signature.name);
53        score += name_score;
54        factors += 1;
55
56        // Language match
57        if pattern.signature.language == signature.language {
58            score += 1.0;
59        }
60        factors += 1;
61
62        // Parameter count similarity
63        let param_diff =
64            (pattern.signature.params.len() as i32 - signature.params.len() as i32).unsigned_abs();
65        let param_score = if param_diff == 0 {
66            1.0
67        } else {
68            1.0 / (1.0 + param_diff as f64)
69        };
70        score += param_score;
71        factors += 1;
72
73        // Return type match
74        if pattern.signature.return_type == signature.return_type {
75            score += 1.0;
76        } else if pattern.signature.return_type.is_some() && signature.return_type.is_some() {
77            score += string_similarity(
78                pattern.signature.return_type.as_deref().unwrap_or(""),
79                signature.return_type.as_deref().unwrap_or(""),
80            );
81        }
82        factors += 1;
83
84        // Async match
85        if pattern.signature.is_async == signature.is_async {
86            score += 0.5;
87        }
88        factors += 1;
89
90        // Parameter type similarity
91        let type_score = param_type_similarity(&pattern.signature.params, &signature.params);
92        score += type_score;
93        factors += 1;
94
95        if factors > 0 {
96            score / factors as f64
97        } else {
98            0.0
99        }
100    }
101}
102
103fn string_similarity(a: &str, b: &str) -> f64 {
104    if a == b {
105        return 1.0;
106    }
107    let a_lower = a.to_lowercase();
108    let b_lower = b.to_lowercase();
109    if a_lower == b_lower {
110        return 0.95;
111    }
112    // Simple substring match
113    if a_lower.contains(&b_lower) || b_lower.contains(&a_lower) {
114        return 0.7;
115    }
116    // Levenshtein-based similarity
117    let max_len = a.len().max(b.len());
118    if max_len == 0 {
119        return 1.0;
120    }
121    let dist = levenshtein_distance(&a_lower, &b_lower);
122    1.0 - (dist as f64 / max_len as f64)
123}
124
125fn levenshtein_distance(a: &str, b: &str) -> usize {
126    let a_chars: Vec<char> = a.chars().collect();
127    let b_chars: Vec<char> = b.chars().collect();
128    let m = a_chars.len();
129    let n = b_chars.len();
130    let mut dp = vec![vec![0usize; n + 1]; m + 1];
131    for (i, row) in dp.iter_mut().enumerate().take(m + 1) {
132        row[0] = i;
133    }
134    for (j, cell) in dp[0].iter_mut().enumerate().take(n + 1) {
135        *cell = j;
136    }
137    for i in 1..=m {
138        for j in 1..=n {
139            let cost = if a_chars[i - 1] == b_chars[j - 1] {
140                0
141            } else {
142                1
143            };
144            dp[i][j] = (dp[i - 1][j] + 1)
145                .min(dp[i][j - 1] + 1)
146                .min(dp[i - 1][j - 1] + cost);
147        }
148    }
149    dp[m][n]
150}
151
152fn param_type_similarity(
153    a_params: &[crate::types::pattern::ParamSignature],
154    b_params: &[crate::types::pattern::ParamSignature],
155) -> f64 {
156    if a_params.is_empty() && b_params.is_empty() {
157        return 1.0;
158    }
159    let max_len = a_params.len().max(b_params.len());
160    if max_len == 0 {
161        return 1.0;
162    }
163    let matches: f64 = a_params
164        .iter()
165        .zip(b_params.iter())
166        .map(|(a, b)| string_similarity(&a.param_type, &b.param_type))
167        .sum();
168    matches / max_len as f64
169}