Skip to main content

palladium/errors/
suggestions.rs

1// Common error patterns and suggestions for Palladium
2// "Learning from mistakes, one suggestion at a time"
3
4// Removed unused imports
5
6/// Provides intelligent suggestions based on error patterns
7pub struct SuggestionEngine;
8
9impl SuggestionEngine {
10    /// Suggest similar identifier names (for typos)
11    pub fn suggest_similar_name(name: &str, available: &[String]) -> Option<String> {
12        let name_lower = name.to_lowercase();
13
14        // Find exact case-insensitive match first
15        for candidate in available {
16            if candidate.to_lowercase() == name_lower {
17                return Some(candidate.clone());
18            }
19        }
20
21        // Find similar names using edit distance
22        let mut best_match = None;
23        let mut best_distance = usize::MAX;
24
25        for candidate in available {
26            let distance = Self::edit_distance(name, candidate);
27
28            // Only suggest if the distance is reasonable (less than 1/3 of the length)
29            if distance < best_distance && distance <= name.len() / 3 + 1 {
30                best_distance = distance;
31                best_match = Some(candidate.clone());
32            }
33        }
34
35        best_match
36    }
37
38    /// Calculate Levenshtein edit distance between two strings
39    fn edit_distance(a: &str, b: &str) -> usize {
40        let a_chars: Vec<char> = a.chars().collect();
41        let b_chars: Vec<char> = b.chars().collect();
42        let a_len = a_chars.len();
43        let b_len = b_chars.len();
44
45        if a_len == 0 {
46            return b_len;
47        }
48        if b_len == 0 {
49            return a_len;
50        }
51
52        let mut matrix = vec![vec![0; b_len + 1]; a_len + 1];
53
54        // Initialize first column and row
55        for (i, row) in matrix.iter_mut().enumerate().take(a_len + 1) {
56            row[0] = i;
57        }
58        for j in 0..=b_len {
59            matrix[0][j] = j;
60        }
61
62        // Fill the matrix
63        for i in 1..=a_len {
64            for j in 1..=b_len {
65                let cost = if a_chars[i - 1] == b_chars[j - 1] {
66                    0
67                } else {
68                    1
69                };
70                matrix[i][j] = (matrix[i - 1][j] + 1) // deletion
71                    .min(matrix[i][j - 1] + 1) // insertion
72                    .min(matrix[i - 1][j - 1] + cost); // substitution
73            }
74        }
75
76        matrix[a_len][b_len]
77    }
78
79    /// Check if a character looks like a quote that should be ASCII
80    pub fn is_fancy_quote(ch: char) -> bool {
81        matches!(
82            ch,
83            '\u{201C}' | '\u{201D}' | '\u{2018}' | '\u{2019}' | '`' | '\u{00B4}'
84        )
85    }
86
87    /// Get the ASCII equivalent of a fancy quote
88    pub fn suggest_ascii_quote(ch: char) -> Option<char> {
89        match ch {
90            '\u{201C}' | '\u{201D}' => Some('"'),
91            '\u{2018}' | '\u{2019}' | '`' | '\u{00B4}' => Some('\''),
92            _ => None,
93        }
94    }
95
96    /// Common beginner mistakes with C-style syntax
97    pub fn suggest_for_c_style_mistake(code: &str) -> Option<String> {
98        if code.contains("++") {
99            Some(
100                "Palladium doesn't have ++ operator. Use 'x = x + 1' or 'x += 1' instead"
101                    .to_string(),
102            )
103        } else if code.contains("--") {
104            Some(
105                "Palladium doesn't have -- operator. Use 'x = x - 1' or 'x -= 1' instead"
106                    .to_string(),
107            )
108        } else if code.contains("==") && code.contains("=") {
109            Some("Make sure you're using '=' for assignment and '==' for comparison".to_string())
110        } else if code.starts_with("#include") {
111            Some(
112                "Palladium uses 'import' instead of '#include'. Example: import std.io;"
113                    .to_string(),
114            )
115        } else if code.contains("malloc") || code.contains("free") {
116            Some(
117                "Palladium has automatic memory management. You don't need malloc/free".to_string(),
118            )
119        } else {
120            None
121        }
122    }
123
124    /// Suggest fixes for common type errors
125    pub fn suggest_type_conversion(from_type: &str, to_type: &str) -> Option<String> {
126        match (
127            from_type.to_lowercase().as_str(),
128            to_type.to_lowercase().as_str(),
129        ) {
130            ("int" | "i64", "string") => {
131                Some("Use int_to_string() to convert int to string".to_string())
132            }
133            ("string", "int" | "i64") => {
134                Some("Use parse_int() to convert string to int".to_string())
135            }
136            ("float", "int" | "i64") => Some("Use to_int() to convert float to int".to_string()),
137            ("int" | "i64", "float") => Some("Use to_float() to convert int to float".to_string()),
138            ("bool", "string") => Some("Use to_string() to convert bool to string".to_string()),
139            ("string", "bool") => Some("Use parse_bool() to convert string to bool".to_string()),
140            _ => None,
141        }
142    }
143
144    /// Suggest fixes for missing imports
145    pub fn suggest_import_for_function(func_name: &str) -> Option<String> {
146        match func_name {
147            "println" | "print" | "readln" => Some("import std.io;".to_string()),
148            "sqrt" | "pow" | "abs" | "sin" | "cos" => Some("import std.math;".to_string()),
149            "len" | "substr" | "concat" => Some("import std.string;".to_string()),
150            "Vec" | "HashMap" | "Set" => Some("import std.collections;".to_string()),
151            _ => None,
152        }
153    }
154
155    /// Check if parentheses are balanced
156    pub fn check_balanced_delimiters(code: &str) -> Option<String> {
157        let mut stack = Vec::new();
158
159        for (i, ch) in code.chars().enumerate() {
160            match ch {
161                '(' | '[' | '{' => stack.push((ch, i)),
162                ')' => {
163                    if let Some((open, _)) = stack.pop() {
164                        if open != '(' {
165                            return Some(format!(
166                                "Mismatched parentheses: expected '{}' to match '{}'",
167                                Self::matching_delimiter(open),
168                                open
169                            ));
170                        }
171                    } else {
172                        return Some("Unmatched closing parenthesis ')'".to_string());
173                    }
174                }
175                ']' => {
176                    if let Some((open, _)) = stack.pop() {
177                        if open != '[' {
178                            return Some(format!(
179                                "Mismatched brackets: expected '{}' to match '{}'",
180                                Self::matching_delimiter(open),
181                                open
182                            ));
183                        }
184                    } else {
185                        return Some("Unmatched closing bracket ']'".to_string());
186                    }
187                }
188                '}' => {
189                    if let Some((open, _)) = stack.pop() {
190                        if open != '{' {
191                            return Some(format!(
192                                "Mismatched braces: expected '{}' to match '{}'",
193                                Self::matching_delimiter(open),
194                                open
195                            ));
196                        }
197                    } else {
198                        return Some("Unmatched closing brace '}'".to_string());
199                    }
200                }
201                _ => {}
202            }
203        }
204
205        if let Some((open, _)) = stack.pop() {
206            Some(format!(
207                "Unclosed delimiter '{}', expected '{}'",
208                open,
209                Self::matching_delimiter(open)
210            ))
211        } else {
212            None
213        }
214    }
215
216    fn matching_delimiter(open: char) -> char {
217        match open {
218            '(' => ')',
219            '[' => ']',
220            '{' => '}',
221            _ => open,
222        }
223    }
224}
225
226/// Common patterns that beginners might use incorrectly
227pub struct BeginnerPatterns;
228
229impl BeginnerPatterns {
230    pub fn check_pattern(code: &str) -> Vec<String> {
231        let mut suggestions = Vec::new();
232
233        // Check for printf-style formatting
234        if code.contains("%d") || code.contains("%s") || code.contains("%f") {
235            suggestions.push(
236                "Palladium uses string interpolation instead of printf-style formatting. \
237                 Example: println(\"x = {}\", x);"
238                    .to_string(),
239            );
240        }
241
242        // Check for null/nil/None
243        if code.contains("null") || code.contains("nil") || code.contains("NULL") {
244            suggestions.push(
245                "Palladium uses Option types instead of null. \
246                 Use 'Option<T>' and 'Some(value)' or 'None'"
247                    .to_string(),
248            );
249        }
250
251        // Check for var keyword
252        if code.contains("var ") {
253            suggestions.push(
254                "Palladium uses 'let' for variables and 'let mut' for mutable variables"
255                    .to_string(),
256            );
257        }
258
259        // Check for const keyword at wrong position
260        if code.contains("const ") && !code.trim_start().starts_with("const") {
261            suggestions.push(
262                "In Palladium, 'const' should be used at the top level for constants".to_string(),
263            );
264        }
265
266        // Check for class keyword
267        if code.contains("class ") {
268            suggestions.push(
269                "Palladium uses 'struct' for data types. Classes are not supported yet".to_string(),
270            );
271        }
272
273        // Check for switch statement
274        if code.contains("switch") {
275            suggestions.push(
276                "Palladium uses 'match' expressions instead of switch statements".to_string(),
277            );
278        }
279
280        // Check for do-while
281        if code.contains("do {") || code.contains("do{") {
282            suggestions.push(
283                "Palladium doesn't have do-while loops. Use 'loop { ... if condition { break; } }'"
284                    .to_string(),
285            );
286        }
287
288        suggestions
289    }
290}