tensorlogic_cli/
error_suggestions.rs

1//! Error handling with helpful suggestions
2//!
3//! Provides user-friendly error messages with actionable suggestions.
4
5#![allow(dead_code)]
6
7use anyhow::Result;
8use std::fmt;
9
10/// Error with suggestions for resolution
11#[derive(Debug)]
12pub struct ErrorWithSuggestions {
13    pub error: String,
14    pub suggestions: Vec<String>,
15    pub examples: Vec<String>,
16}
17
18impl fmt::Display for ErrorWithSuggestions {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        writeln!(f, "Error: {}", self.error)?;
21
22        if !self.suggestions.is_empty() {
23            writeln!(f, "\nSuggestions:")?;
24            for suggestion in &self.suggestions {
25                writeln!(f, "  • {}", suggestion)?;
26            }
27        }
28
29        if !self.examples.is_empty() {
30            writeln!(f, "\nExamples:")?;
31            for example in &self.examples {
32                writeln!(f, "  {}", example)?;
33            }
34        }
35
36        Ok(())
37    }
38}
39
40/// Enhance compilation errors with helpful context
41pub fn enhance_compilation_error(error: &str) -> ErrorWithSuggestions {
42    let error_lower = error.to_lowercase();
43
44    // Detect common error patterns and provide suggestions
45    if error_lower.contains("free variable") || error_lower.contains("unbound") {
46        ErrorWithSuggestions {
47            error: error.to_string(),
48            suggestions: vec![
49                "Add a quantifier (EXISTS or FORALL) for unbound variables".to_string(),
50                "Define a domain for the variable using --domains".to_string(),
51                "Check variable names for typos".to_string(),
52            ],
53            examples: vec![
54                "EXISTS x IN Person. knows(x, alice)".to_string(),
55                "FORALL x IN Person. mortal(x)".to_string(),
56                "tensorlogic \"expr\" --domains Person:100".to_string(),
57            ],
58        }
59    } else if error_lower.contains("arity") || error_lower.contains("argument") {
60        ErrorWithSuggestions {
61            error: error.to_string(),
62            suggestions: vec![
63                "Check the number of arguments in predicate calls".to_string(),
64                "Verify predicate signatures match their usage".to_string(),
65                "Ensure all arguments are properly specified".to_string(),
66            ],
67            examples: vec![
68                "knows(x, y)          # Binary predicate".to_string(),
69                "person(x)            # Unary predicate".to_string(),
70                "located(x, y, z)     # Ternary predicate".to_string(),
71            ],
72        }
73    } else if error_lower.contains("type") {
74        ErrorWithSuggestions {
75            error: error.to_string(),
76            suggestions: vec![
77                "Check that operations use compatible types".to_string(),
78                "Verify arithmetic is only used with numeric expressions".to_string(),
79                "Use appropriate comparison operators for the data type".to_string(),
80            ],
81            examples: vec![
82                "age(x) + 10          # Arithmetic on numeric values".to_string(),
83                "name(x) = \"alice\"    # String comparison".to_string(),
84                "score(x) > threshold # Numeric comparison".to_string(),
85            ],
86        }
87    } else if error_lower.contains("syntax") || error_lower.contains("parse") {
88        ErrorWithSuggestions {
89            error: error.to_string(),
90            suggestions: vec![
91                "Check for unmatched parentheses".to_string(),
92                "Verify operator spelling (AND, OR, NOT, IMPLIES)".to_string(),
93                "Use quotes for string literals".to_string(),
94                "Ensure quantifiers have IN domain clause".to_string(),
95            ],
96            examples: vec![
97                "(p AND q) OR r       # Grouped expression".to_string(),
98                "EXISTS x IN D. p(x)  # Quantifier with domain".to_string(),
99                "knows(\"alice\", bob)  # String literal in quotes".to_string(),
100            ],
101        }
102    } else if error_lower.contains("domain") {
103        ErrorWithSuggestions {
104            error: error.to_string(),
105            suggestions: vec![
106                "Define the domain using --domains option".to_string(),
107                "Check domain name spelling in quantifiers".to_string(),
108                "Ensure domain size is positive".to_string(),
109            ],
110            examples: vec![
111                "tensorlogic expr --domains Person:100".to_string(),
112                "tensorlogic expr --domains User:1000 --domains Item:5000".to_string(),
113                "EXISTS x IN Person. knows(x, y)  # Domain must be defined".to_string(),
114            ],
115        }
116    } else if error_lower.contains("validation") {
117        ErrorWithSuggestions {
118            error: error.to_string(),
119            suggestions: vec![
120                "Check that all inputs and outputs are properly connected".to_string(),
121                "Verify that the graph structure is valid".to_string(),
122                "Ensure all tensors have producers if required".to_string(),
123                "Use --debug to see detailed graph structure".to_string(),
124            ],
125            examples: vec![
126                "tensorlogic expr --debug      # Show detailed compilation info".to_string(),
127                "tensorlogic expr --no-validate # Skip validation".to_string(),
128            ],
129        }
130    } else if error_lower.contains("strategy") {
131        ErrorWithSuggestions {
132            error: error.to_string(),
133            suggestions: vec![
134                "Use one of the 6 valid compilation strategies".to_string(),
135                "Check strategy name spelling".to_string(),
136            ],
137            examples: vec![
138                "--strategy soft_differentiable   # For neural training".to_string(),
139                "--strategy hard_boolean          # For discrete logic".to_string(),
140                "--strategy fuzzy_godel           # For Gödel fuzzy logic".to_string(),
141                "--strategy fuzzy_product         # For product fuzzy logic".to_string(),
142                "--strategy fuzzy_lukasiewicz     # For Łukasiewicz logic".to_string(),
143                "--strategy probabilistic         # For probabilities".to_string(),
144            ],
145        }
146    } else {
147        // Generic error with general suggestions
148        ErrorWithSuggestions {
149            error: error.to_string(),
150            suggestions: vec![
151                "Use --debug flag to see detailed error information".to_string(),
152                "Check the expression syntax and formatting".to_string(),
153                "Consult the documentation for correct usage".to_string(),
154            ],
155            examples: vec![
156                "tensorlogic expr --debug".to_string(),
157                "tensorlogic --help".to_string(),
158            ],
159        }
160    }
161}
162
163/// Enhance file operation errors
164pub fn enhance_file_error(path: &str, error: &str) -> ErrorWithSuggestions {
165    let error_lower = error.to_lowercase();
166
167    if error_lower.contains("not found") || error_lower.contains("no such") {
168        ErrorWithSuggestions {
169            error: format!("File not found: {}", path),
170            suggestions: vec![
171                "Check the file path spelling and location".to_string(),
172                "Use absolute path or relative path from current directory".to_string(),
173                "Verify the file exists using ls or find command".to_string(),
174            ],
175            examples: vec![
176                format!("ls {}", path),
177                format!(
178                    "find . -name \"{}\"",
179                    path.rsplit('/').next().unwrap_or(path)
180                ),
181            ],
182        }
183    } else if error_lower.contains("permission") {
184        ErrorWithSuggestions {
185            error: format!("Permission denied: {}", path),
186            suggestions: vec![
187                "Check file permissions".to_string(),
188                "Ensure you have read access to the file".to_string(),
189                "Try using sudo if appropriate".to_string(),
190            ],
191            examples: vec![format!("ls -l {}", path), format!("chmod +r {}", path)],
192        }
193    } else {
194        ErrorWithSuggestions {
195            error: format!("File error for {}: {}", path, error),
196            suggestions: vec![
197                "Check file permissions and accessibility".to_string(),
198                "Verify disk space is available".to_string(),
199            ],
200            examples: vec!["df -h".to_string()],
201        }
202    }
203}
204
205/// Provide helpful error context for common CLI mistakes
206pub fn suggest_for_cli_args(error: &str) -> Result<()> {
207    let error_lower = error.to_lowercase();
208
209    if error_lower.contains("unexpected argument") || error_lower.contains("found argument") {
210        let suggestions = ErrorWithSuggestions {
211            error: error.to_string(),
212            suggestions: vec![
213                "Check argument spelling and dashes (- vs --)".to_string(),
214                "Use --help to see all available arguments".to_string(),
215                "Some arguments require values (e.g., --domains Person:100)".to_string(),
216            ],
217            examples: vec![
218                "tensorlogic --help".to_string(),
219                "tensorlogic expr --domains Person:100  # Correct".to_string(),
220                "tensorlogic expr --domain Person:100   # Wrong (should be --domains)".to_string(),
221            ],
222        };
223
224        Err(anyhow::anyhow!("{}", suggestions))
225    } else if error_lower.contains("required") {
226        let suggestions = ErrorWithSuggestions {
227            error: error.to_string(),
228            suggestions: vec![
229                "Provide the required input expression or file".to_string(),
230                "Use a subcommand (repl, batch, etc.) or provide input".to_string(),
231            ],
232            examples: vec![
233                "tensorlogic \"knows(x, y)\"        # Direct expression".to_string(),
234                "tensorlogic file.tl              # From file".to_string(),
235                "tensorlogic repl                 # Interactive mode".to_string(),
236            ],
237        };
238
239        Err(anyhow::anyhow!("{}", suggestions))
240    } else {
241        Err(anyhow::anyhow!("{}", error))
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_enhance_free_variable_error() {
251        let error = enhance_compilation_error("free variable 'x' found in expression");
252
253        assert!(error.error.contains("free variable"));
254        assert!(!error.suggestions.is_empty());
255        assert!(error.suggestions[0].contains("quantifier"));
256        assert!(!error.examples.is_empty());
257    }
258
259    #[test]
260    fn test_enhance_arity_error() {
261        let error = enhance_compilation_error("arity mismatch: expected 2 arguments");
262
263        assert!(error.error.contains("arity"));
264        assert!(!error.suggestions.is_empty());
265        assert!(!error.examples.is_empty());
266    }
267
268    #[test]
269    fn test_enhance_strategy_error() {
270        let error = enhance_compilation_error("unknown strategy: foo_bar");
271
272        assert!(error.error.contains("strategy"));
273        assert!(!error.suggestions.is_empty());
274        assert!(error.examples.len() >= 6); // All 6 strategies
275    }
276
277    #[test]
278    fn test_enhance_file_error() {
279        let error = enhance_file_error("/path/to/file.tl", "No such file or directory");
280
281        assert!(error.error.contains("not found"));
282        assert!(!error.suggestions.is_empty());
283        assert!(!error.examples.is_empty());
284    }
285}