agentic_evolve_core/matching/
fuzzy.rs1use crate::types::error::EvolveResult;
4use crate::types::match_result::{MatchContext, MatchResult, MatchScore};
5use crate::types::pattern::{FunctionSignature, Pattern};
6
7#[derive(Debug)]
9pub struct FuzzyMatcher {
10 threshold: f64,
11}
12
13impl FuzzyMatcher {
14 pub fn new(threshold: f64) -> Self {
15 Self { threshold }
16 }
17
18 pub fn find_matches(
19 &self,
20 signature: &FunctionSignature,
21 patterns: &[&Pattern],
22 _context: &MatchContext,
23 limit: usize,
24 ) -> EvolveResult<Vec<MatchResult>> {
25 let mut results: Vec<MatchResult> = patterns
26 .iter()
27 .map(|p| {
28 let score = self.score_fuzzy(p, signature);
29 MatchResult {
30 pattern_id: p.id.clone(),
31 pattern: (*p).clone(),
32 score: MatchScore::from_single(score),
33 suggested_bindings: std::collections::HashMap::new(),
34 }
35 })
36 .filter(|r| r.score.combined >= self.threshold)
37 .collect();
38
39 results.sort_by(|a, b| {
40 b.score
41 .combined
42 .partial_cmp(&a.score.combined)
43 .unwrap_or(std::cmp::Ordering::Equal)
44 });
45 results.truncate(limit);
46 Ok(results)
47 }
48
49 fn score_fuzzy(&self, pattern: &Pattern, signature: &FunctionSignature) -> f64 {
50 let name_sim = fuzzy_similarity(&pattern.signature.name, &signature.name);
51 let lang_match = if pattern.signature.language == signature.language {
52 1.0
53 } else {
54 0.5
55 };
56
57 let param_sim = if pattern.signature.params.is_empty() && signature.params.is_empty() {
58 1.0
59 } else {
60 let max_params = pattern.signature.params.len().max(signature.params.len());
61 let matching = pattern
62 .signature
63 .params
64 .iter()
65 .zip(signature.params.iter())
66 .filter(|(a, b)| fuzzy_similarity(&a.param_type, &b.param_type) > 0.6)
67 .count();
68 if max_params == 0 {
69 1.0
70 } else {
71 matching as f64 / max_params as f64
72 }
73 };
74
75 name_sim * 0.5 + lang_match * 0.2 + param_sim * 0.3
76 }
77}
78
79impl Default for FuzzyMatcher {
80 fn default() -> Self {
81 Self::new(0.3)
82 }
83}
84
85fn fuzzy_similarity(a: &str, b: &str) -> f64 {
86 if a == b {
87 return 1.0;
88 }
89 let a_lower = a.to_lowercase();
90 let b_lower = b.to_lowercase();
91 if a_lower == b_lower {
92 return 0.95;
93 }
94
95 let a_trigrams = trigrams(&a_lower);
97 let b_trigrams = trigrams(&b_lower);
98 if a_trigrams.is_empty() && b_trigrams.is_empty() {
99 return 1.0;
100 }
101 if a_trigrams.is_empty() || b_trigrams.is_empty() {
102 return 0.0;
103 }
104
105 let intersection = a_trigrams.iter().filter(|t| b_trigrams.contains(t)).count();
106 let union = a_trigrams.len() + b_trigrams.len() - intersection;
107 if union == 0 {
108 return 0.0;
109 }
110 intersection as f64 / union as f64
111}
112
113fn trigrams(s: &str) -> Vec<String> {
114 let chars: Vec<char> = s.chars().collect();
115 if chars.len() < 3 {
116 return vec![s.to_string()];
117 }
118 chars.windows(3).map(|w| w.iter().collect()).collect()
119}