devalang_core/core/error/
mod.rs

1use crate::core::parser::{
2    driver::Parser,
3    statement::{Statement, StatementKind},
4};
5use devalang_types::Value;
6use serde::{Deserialize, Serialize};
7
8pub struct ErrorHandler {
9    errors: Vec<Error>,
10}
11
12#[derive(Serialize, Deserialize, Clone, Debug)]
13pub enum Severity {
14    Warning,
15    Critical,
16}
17
18#[derive(Serialize, Deserialize, Clone, Debug, Default)]
19pub struct StackFrame {
20    pub module: Option<String>,
21    pub context: Option<String>,
22    pub line: usize,
23    pub column: usize,
24}
25
26#[derive(Serialize, Deserialize, Clone, Debug)]
27pub struct ErrorResult {
28    pub message: String,
29    pub line: usize,
30    pub column: usize,
31    pub severity: Severity,
32    pub stack: Vec<StackFrame>,
33}
34
35#[derive(Clone)]
36pub struct Error {
37    pub message: String,
38    pub line: usize,
39    pub column: usize,
40}
41
42impl Default for ErrorHandler {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl ErrorHandler {
49    pub fn new() -> Self {
50        Self { errors: Vec::new() }
51    }
52
53    pub fn add_error(&mut self, message: String, line: usize, column: usize) {
54        let error_statement = Error {
55            message,
56            line,
57            column,
58        };
59        self.errors.push(error_statement);
60    }
61
62    pub fn has_errors(&self) -> bool {
63        !self.errors.is_empty()
64    }
65
66    pub fn get_errors(&self) -> &Vec<Error> {
67        &self.errors
68    }
69
70    pub fn clear_errors(&mut self) {
71        self.errors.clear();
72    }
73
74    pub fn detect_from_statements(&mut self, _parser: &mut Parser, statements: &[Statement]) {
75        for stmt in statements {
76            match &stmt.kind {
77                StatementKind::Unknown => {
78                    self.add_error("Unknown statement".to_string(), stmt.line, stmt.column);
79                }
80                StatementKind::Error { message } => {
81                    self.add_error(message.clone(), stmt.line, stmt.column);
82                }
83                _ => {}
84            }
85        }
86    }
87}
88
89/// Collects errors recursively from statements (mirrors old utils implementation).
90pub fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
91    let mut errors: Vec<ErrorResult> = Vec::new();
92
93    for stmt in statements {
94        match &stmt.kind {
95            StatementKind::Unknown => {
96                errors.push(ErrorResult {
97                    message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
98                    line: stmt.line,
99                    column: stmt.column,
100                    severity: Severity::Warning,
101                    stack: vec![StackFrame {
102                        module: None,
103                        context: Some("Unknown".to_string()),
104                        line: stmt.line,
105                        column: stmt.column,
106                    }],
107                });
108            }
109            StatementKind::Error { message } => {
110                errors.push(ErrorResult {
111                    message: message.clone(),
112                    line: stmt.line,
113                    column: stmt.column,
114                    severity: Severity::Critical,
115                    stack: vec![StackFrame {
116                        module: None,
117                        context: Some("Error".to_string()),
118                        line: stmt.line,
119                        column: stmt.column,
120                    }],
121                });
122            }
123            StatementKind::Loop => {
124                if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
125                    let nested = collect_errors_recursively(body_statements);
126                    errors.extend(nested.into_iter().map(|mut e| {
127                        e.stack.insert(
128                            0,
129                            StackFrame {
130                                module: None,
131                                context: Some("loop".to_string()),
132                                line: stmt.line,
133                                column: stmt.column,
134                            },
135                        );
136                        e
137                    }));
138                }
139            }
140            _ => {}
141        }
142    }
143
144    errors
145}
146
147fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
148    if let Value::Map(map) = value {
149        if let Some(Value::Block(statements)) = map.get("body") {
150            return Some(statements);
151        }
152    }
153    None
154}
155
156pub fn partition_errors(errors: Vec<ErrorResult>) -> (Vec<ErrorResult>, Vec<ErrorResult>) {
157    let mut warnings = Vec::new();
158    let mut criticals = Vec::new();
159    for e in errors {
160        match e.severity {
161            Severity::Warning => warnings.push(e),
162            Severity::Critical => criticals.push(e),
163        }
164    }
165    (warnings, criticals)
166}
167
168pub fn log_errors_with_stack(prefix: &str, warnings: &[ErrorResult], criticals: &[ErrorResult]) {
169    use devalang_utils::logger::LogLevel;
170    use devalang_utils::logger::Logger;
171
172    let logger = Logger::new();
173    if !warnings.is_empty() {
174        logger.log_message(
175            LogLevel::Warning,
176            &format!("{}: {} warning(s)", prefix, warnings.len()),
177        );
178        for w in warnings {
179            logger.log_message(LogLevel::Warning, &format!("- {}", w.message));
180            if let Some(frame) = w.stack.first() {
181                let module = frame.module.clone().unwrap_or_default();
182                logger.log_message(
183                    LogLevel::Debug,
184                    &format!(
185                        "     ↳ {}:{}:{} {}",
186                        module,
187                        frame.line,
188                        frame.column,
189                        frame.context.clone().unwrap_or_default()
190                    ),
191                );
192            }
193            if w.stack.len() > 1 {
194                for (i, f) in w.stack.iter().enumerate().skip(1) {
195                    let module = f.module.clone().unwrap_or_default();
196                    logger.log_message(
197                        LogLevel::Debug,
198                        &format!(
199                            "       #{} {}:{}:{} {}",
200                            i,
201                            module,
202                            f.line,
203                            f.column,
204                            f.context.clone().unwrap_or_default()
205                        ),
206                    );
207                }
208            }
209        }
210    }
211    if !criticals.is_empty() {
212        logger.log_message(
213            LogLevel::Error,
214            &format!("{}: {} critical error(s)", prefix, criticals.len()),
215        );
216        for c in criticals {
217            logger.log_message(LogLevel::Error, &format!("- {}", c.message));
218            if let Some(frame) = c.stack.first() {
219                let module = frame.module.clone().unwrap_or_default();
220                logger.log_message(
221                    LogLevel::Error,
222                    &format!(
223                        "     ↳ {}:{}:{} {}",
224                        module,
225                        frame.line,
226                        frame.column,
227                        frame.context.clone().unwrap_or_default()
228                    ),
229                );
230            }
231            if c.stack.len() > 1 {
232                for (i, f) in c.stack.iter().enumerate().skip(1) {
233                    let module = f.module.clone().unwrap_or_default();
234                    logger.log_message(
235                        LogLevel::Error,
236                        &format!(
237                            "       #{} {}:{}:{} {}",
238                            i,
239                            module,
240                            f.line,
241                            f.column,
242                            f.context.clone().unwrap_or_default()
243                        ),
244                    );
245                }
246            }
247        }
248    }
249}
250
251/// Collects errors from all modules and annotates stack frames with module names.
252pub fn collect_all_errors_with_modules(
253    statements_by_module: &std::collections::HashMap<String, Vec<Statement>>,
254) -> Vec<ErrorResult> {
255    let mut all: Vec<ErrorResult> = Vec::new();
256    for (module, stmts) in statements_by_module.iter() {
257        let mut errs = collect_errors_recursively(stmts);
258        for e in errs.iter_mut() {
259            // annotate first stack frame module if missing
260            if let Some(first) = e.stack.first_mut() {
261                if first.module.is_none() {
262                    first.module = Some(module.clone());
263                }
264            }
265        }
266        all.extend(errs.into_iter());
267    }
268    all
269}