#![allow(dead_code)]
use crate::error::recovery_v2::{ErrorContext, RecoverySuggestion, SuggestionCategory};
use crate::error::{Error, Span};
use rustc_hash::FxHashMap;
pub struct MLPatternRecognizer {
feature_extractors: Vec<Box<dyn FeatureExtractor>>,
patterns: FxHashMap<String, TrainedPattern>,
}
trait FeatureExtractor {
fn extract(&self, context: &ErrorContext) -> Vec<Feature>;
}
#[derive(Debug, Clone)]
struct Feature {
name: String,
value: f64,
weight: f64,
}
#[derive(Debug, Clone)]
struct TrainedPattern {
id: String,
weights: FxHashMap<String, f64>,
bias: f64,
success_rate: f64,
usage_count: usize,
fix_template: FixTemplate,
}
#[derive(Debug, Clone)]
enum FixTemplate {
InsertChar { char: char, offset: i32 },
InsertString { string: String, offset: i32 },
ReplaceRange {
start: i32,
end: i32,
replacement: String,
},
RemoveRange { start: i32, end: i32 },
Complex(Vec<FixOperation>),
}
#[derive(Debug, Clone)]
enum FixOperation {
Insert {
position: usize,
text: String,
},
Delete {
start: usize,
end: usize,
},
Replace {
start: usize,
end: usize,
text: String,
},
}
#[derive(Debug, Clone)]
struct SuccessfulFix {
error_signature: String,
}
impl MLPatternRecognizer {
pub fn new() -> Self {
let mut recognizer = MLPatternRecognizer {
feature_extractors: Vec::new(),
patterns: FxHashMap::default(),
};
recognizer.add_default_extractors();
recognizer.load_pretrained_patterns();
recognizer
}
fn add_default_extractors(&mut self) {
self.feature_extractors
.push(Box::new(TokenPatternExtractor));
self.feature_extractors
.push(Box::new(CharacterDistributionExtractor));
self.feature_extractors
.push(Box::new(StructuralBalanceExtractor));
self.feature_extractors.push(Box::new(ContextualExtractor));
self.feature_extractors.push(Box::new(ErrorTypeExtractor));
}
fn load_pretrained_patterns(&mut self) {
self.patterns.insert(
"missing_closing_brace".to_string(),
TrainedPattern {
id: "missing_closing_brace".to_string(),
weights: [
("unmatched_open_brace".to_string(), 0.9),
("at_end_of_input".to_string(), 0.8),
("in_object_context".to_string(), 0.7),
]
.iter()
.cloned()
.collect(),
bias: -0.1,
success_rate: 0.95,
usage_count: 0,
fix_template: FixTemplate::InsertChar {
char: '}',
offset: 0,
},
},
);
self.patterns.insert(
"missing_closing_bracket".to_string(),
TrainedPattern {
id: "missing_closing_bracket".to_string(),
weights: [
("unmatched_open_bracket".to_string(), 0.9),
("at_end_of_input".to_string(), 0.8),
("in_array_context".to_string(), 0.7),
]
.iter()
.cloned()
.collect(),
bias: -0.1,
success_rate: 0.95,
usage_count: 0,
fix_template: FixTemplate::InsertChar {
char: ']',
offset: 0,
},
},
);
self.patterns.insert(
"missing_comma".to_string(),
TrainedPattern {
id: "missing_comma".to_string(),
weights: [
("consecutive_values".to_string(), 0.9),
("after_string_or_number".to_string(), 0.8),
("before_string_or_brace".to_string(), 0.7),
]
.iter()
.cloned()
.collect(),
bias: -0.2,
success_rate: 0.85,
usage_count: 0,
fix_template: FixTemplate::InsertChar {
char: ',',
offset: 0,
},
},
);
self.patterns.insert(
"unmatched_quote".to_string(),
TrainedPattern {
id: "unmatched_quote".to_string(),
weights: [
("odd_quote_count".to_string(), 0.95),
("unterminated_string_error".to_string(), 0.9),
("at_end_of_line".to_string(), 0.6),
]
.iter()
.cloned()
.collect(),
bias: -0.1,
success_rate: 0.9,
usage_count: 0,
fix_template: FixTemplate::InsertChar {
char: '"',
offset: 0,
},
},
);
}
pub fn recognize_and_suggest(&mut self, context: &ErrorContext) -> Vec<RecoverySuggestion> {
let features = self.extract_features(context);
let mut pattern_scores: Vec<(String, f64)> = self
.patterns
.iter()
.map(|(id, pattern)| {
let score = self.calculate_pattern_score(pattern, &features);
(id.clone(), score)
})
.collect();
pattern_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
let mut suggestions = Vec::new();
for (pattern_id, score) in pattern_scores {
if score > 0.5 {
if let Some(pattern) = self.patterns.get(&pattern_id) {
if let Some(suggestion) = self.generate_suggestion(pattern, context, score) {
suggestions.push(suggestion);
}
}
}
}
suggestions
}
fn extract_features(&self, context: &ErrorContext) -> Vec<Feature> {
let mut features = Vec::new();
for extractor in &self.feature_extractors {
features.extend(extractor.extract(context));
}
features
}
fn calculate_pattern_score(&self, pattern: &TrainedPattern, features: &[Feature]) -> f64 {
let mut score = pattern.bias;
for feature in features {
if let Some(weight) = pattern.weights.get(&feature.name) {
score += weight * feature.value;
}
}
1.0 / (1.0 + (-score).exp())
}
fn generate_suggestion(
&self,
pattern: &TrainedPattern,
context: &ErrorContext,
confidence: f64,
) -> Option<RecoverySuggestion> {
let fixed_input = self.apply_fix_template(&pattern.fix_template, context)?;
let category = match pattern.id.as_str() {
"missing_closing_brace" | "missing_closing_bracket" => SuggestionCategory::MissingBracket,
"unmatched_quote" => SuggestionCategory::UnmatchedQuote,
"missing_comma" => SuggestionCategory::MissingComma,
_ => SuggestionCategory::Other,
};
Some(RecoverySuggestion {
description: format!("ML: {}", pattern.id.replace('_', " ")),
confidence: confidence * pattern.success_rate,
fixed_input,
category,
fix_location: Span {
start: context.position,
end: context.position,
},
})
}
fn apply_fix_template(&self, template: &FixTemplate, context: &ErrorContext) -> Option<String> {
match template {
FixTemplate::InsertChar { char, offset } => {
let position = (context.position as i32 + offset).max(0) as usize;
let mut fixed = context.input.clone();
if position <= fixed.len() {
fixed.insert(position, *char);
Some(fixed)
} else {
fixed.push(*char);
Some(fixed)
}
}
FixTemplate::InsertString { string, offset } => {
let position = (context.position as i32 + offset).max(0) as usize;
let mut fixed = context.input.clone();
if position <= fixed.len() {
fixed.insert_str(position, string);
Some(fixed)
} else {
fixed.push_str(string);
Some(fixed)
}
}
FixTemplate::ReplaceRange {
start,
end,
replacement,
} => {
let start_pos = (context.position as i32 + start).max(0) as usize;
let end_pos = (context.position as i32 + end).max(0) as usize;
let mut fixed = context.input.clone();
fixed.replace_range(start_pos..end_pos, replacement);
Some(fixed)
}
FixTemplate::RemoveRange { start, end } => {
let start_pos = (context.position as i32 + start).max(0) as usize;
let end_pos = (context.position as i32 + end).max(0) as usize;
let mut fixed = context.input.clone();
fixed.replace_range(start_pos..end_pos, "");
Some(fixed)
}
FixTemplate::Complex(operations) => {
let mut fixed = context.input.clone();
for op in operations {
match op {
FixOperation::Insert { position, text } => {
if *position <= fixed.len() {
fixed.insert_str(*position, text);
}
}
FixOperation::Delete { start, end } => {
if *start <= fixed.len() && *end <= fixed.len() && start <= end {
fixed.replace_range(*start..*end, "");
}
}
FixOperation::Replace { start, end, text } => {
if *start <= fixed.len() && *end <= fixed.len() && start <= end {
fixed.replace_range(*start..*end, text);
}
}
}
}
Some(fixed)
}
}
}
pub fn update_from_feedback(&mut self, pattern_id: &str, success: bool) {
if let Some(pattern) = self.patterns.get_mut(pattern_id) {
pattern.usage_count += 1;
let alpha = 0.1; pattern.success_rate =
(1.0 - alpha) * pattern.success_rate + alpha * (if success { 1.0 } else { 0.0 });
if !success && pattern.success_rate < 0.5 {
for weight in pattern.weights.values_mut() {
*weight *= 0.95;
}
}
}
}
}
impl Default for MLPatternRecognizer {
fn default() -> Self {
Self::new()
}
}
struct TokenPatternExtractor;
impl FeatureExtractor for TokenPatternExtractor {
fn extract(&self, context: &ErrorContext) -> Vec<Feature> {
let mut features = Vec::new();
if context.tokens_before.len() >= 2 {
let _last_two: Vec<_> = context.tokens_before.iter().rev().take(2).collect();
features.push(Feature {
name: "consecutive_values".to_string(),
value: 0.5, weight: 1.0,
});
}
features
}
}
struct CharacterDistributionExtractor;
impl FeatureExtractor for CharacterDistributionExtractor {
fn extract(&self, context: &ErrorContext) -> Vec<Feature> {
let mut features = Vec::new();
let mut open_braces = 0;
let mut close_braces = 0;
let mut open_brackets = 0;
let mut close_brackets = 0;
let mut quotes = 0;
for ch in context.input.chars() {
match ch {
'{' => open_braces += 1,
'}' => close_braces += 1,
'[' => open_brackets += 1,
']' => close_brackets += 1,
'"' => quotes += 1,
_ => {}
}
}
features.push(Feature {
name: "unmatched_open_brace".to_string(),
value: if open_braces > close_braces { 1.0 } else { 0.0 },
weight: 1.0,
});
features.push(Feature {
name: "unmatched_open_bracket".to_string(),
value: if open_brackets > close_brackets {
1.0
} else {
0.0
},
weight: 1.0,
});
features.push(Feature {
name: "odd_quote_count".to_string(),
value: if quotes % 2 == 1 { 1.0 } else { 0.0 },
weight: 1.0,
});
features
}
}
struct StructuralBalanceExtractor;
impl FeatureExtractor for StructuralBalanceExtractor {
fn extract(&self, context: &ErrorContext) -> Vec<Feature> {
let mut features = Vec::new();
features.push(Feature {
name: "at_end_of_input".to_string(),
value: if context.position >= context.input.len() - 1 {
1.0
} else {
0.0
},
weight: 1.0,
});
features
}
}
struct ContextualExtractor;
impl FeatureExtractor for ContextualExtractor {
fn extract(&self, context: &ErrorContext) -> Vec<Feature> {
let mut features = Vec::new();
features.push(Feature {
name: "in_object_context".to_string(),
value: if context.parsing_context.contains("object") {
1.0
} else {
0.0
},
weight: 1.0,
});
features.push(Feature {
name: "in_array_context".to_string(),
value: if context.parsing_context.contains("array") {
1.0
} else {
0.0
},
weight: 1.0,
});
features
}
}
struct ErrorTypeExtractor;
impl FeatureExtractor for ErrorTypeExtractor {
fn extract(&self, context: &ErrorContext) -> Vec<Feature> {
let mut features = Vec::new();
match &context.error {
Error::UnterminatedString(_) => {
features.push(Feature {
name: "unterminated_string_error".to_string(),
value: 1.0,
weight: 1.0,
});
}
Error::UnexpectedEof(_) | Error::UnexpectedChar(_, _) => {
features.push(Feature {
name: "unexpected_token_error".to_string(),
value: 1.0,
weight: 1.0,
});
}
Error::Expected { expected, found: _, position } => {
if expected.contains("comma") || expected.contains(",") {
features.push(Feature {
name: "expecting_comma".to_string(),
value: 1.0,
weight: 1.0,
});
}
if position > &0 && position < &context.input.len() {
let before = &context.input[..*position];
let after = context.input[*position..].trim_start();
if (before.ends_with('"') || before.chars().last().is_some_and(|c| c.is_numeric())) &&
(after.starts_with('"') || after.starts_with('[') || after.starts_with('{')) {
features.push(Feature {
name: "consecutive_values".to_string(),
value: 1.0,
weight: 1.0,
});
features.push(Feature {
name: "after_string_or_number".to_string(),
value: 1.0,
weight: 1.0,
});
features.push(Feature {
name: "before_string_or_brace".to_string(),
value: 1.0,
weight: 1.0,
});
}
}
}
_ => {}
}
features
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ml_pattern_recognition() {
let mut recognizer = MLPatternRecognizer::new();
let context = ErrorContext {
error: Error::UnexpectedEof(10),
input: r#"{"name": "test""#.to_string(),
position: 15,
tokens_before: vec![],
partial_ast: None,
parsing_context: "in_object".to_string(),
};
let suggestions = recognizer.recognize_and_suggest(&context);
assert!(!suggestions.is_empty());
let first = &suggestions[0];
assert!(first.fixed_input.ends_with('}'));
}
#[test]
fn test_feature_extraction() {
let recognizer = MLPatternRecognizer::new();
let context = ErrorContext {
error: Error::UnterminatedString(5),
input: r#"{"key": "value"#.to_string(),
position: 14,
tokens_before: vec![],
partial_ast: None,
parsing_context: "in_string".to_string(),
};
let features = recognizer.extract_features(&context);
let quote_feature = features
.iter()
.find(|f| f.name == "odd_quote_count")
.expect("Should have odd quote count feature");
assert_eq!(quote_feature.value, 1.0);
}
}