#[cfg(feature = "cli")]
pub mod context_builder;
#[cfg(feature = "cli")]
pub mod suggestions;
#[cfg(feature = "cli")]
pub use context_builder::ErrorContext;
#[cfg(feature = "cli")]
pub use suggestions::suggest_fix;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceLocation {
pub file: String,
pub line: usize,
pub column: usize,
}
impl SourceLocation {
pub fn new(file: impl Into<String>, line: usize, column: usize) -> Self {
Self {
file: file.into(),
line,
column,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorLevel {
Error,
Warning,
Note,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Suggestion {
pub message: String,
pub replacement: Option<String>,
}
impl Suggestion {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
replacement: None,
}
}
pub fn with_replacement(message: impl Into<String>, replacement: impl Into<String>) -> Self {
Self {
message: message.into(),
replacement: Some(replacement.into()),
}
}
}
#[derive(Debug, Clone)]
pub struct CompileError {
pub level: ErrorLevel,
pub message: String,
pub location: SourceLocation,
pub code_snippet: Option<String>,
pub suggestions: Vec<Suggestion>,
}
impl CompileError {
pub fn new(message: impl Into<String>, location: SourceLocation) -> Self {
Self {
level: ErrorLevel::Error,
message: message.into(),
location,
code_snippet: None,
suggestions: Vec::new(),
}
}
pub fn parse_error(
message: impl Into<String>,
file: impl Into<String>,
line: usize,
column: usize,
) -> Self {
Self::new(message, SourceLocation::new(file, line, column))
}
pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
self.code_snippet = Some(snippet.into());
self
}
pub fn with_suggestion(mut self, suggestion: Suggestion) -> Self {
self.suggestions.push(suggestion);
self
}
pub fn suggest(mut self, message: impl Into<String>) -> Self {
self.suggestions.push(Suggestion::new(message));
self
}
}
impl fmt::Display for CompileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let level_str = match self.level {
ErrorLevel::Error => "error",
ErrorLevel::Warning => "warning",
ErrorLevel::Note => "note",
};
writeln!(f, "{}: {}", level_str, self.message)?;
writeln!(
f,
" --> {}:{}:{}",
self.location.file, self.location.line, self.location.column
)?;
if let Some(ref snippet) = self.code_snippet {
writeln!(f, " |")?;
for (i, line) in snippet.lines().enumerate() {
let line_num = self.location.line + i;
writeln!(f, "{:3} | {}", line_num, line)?;
}
writeln!(f, " |")?;
if self.location.column > 0 {
let spaces = " ".repeat(self.location.column - 1);
writeln!(f, " | {}^", spaces)?;
}
}
for suggestion in &self.suggestions {
writeln!(f, " = help: {}", suggestion.message)?;
if let Some(ref replacement) = suggestion.replacement {
writeln!(f, " = suggestion: {}", replacement)?;
}
}
Ok(())
}
}
impl std::error::Error for CompileError {}
pub type CompileResult<T> = Result<T, CompileError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let error = CompileError::parse_error("Expected ']', got '}'", "test.wj", 3, 15)
.with_snippet(" let x = [1, 2, 3")
.suggest("Add ']' before the newline");
let output = format!("{}", error);
assert!(output.contains("error: Expected ']', got '}'"));
assert!(output.contains("test.wj:3:15"));
assert!(output.contains("help: Add ']' before the newline"));
}
}