use serde::{Deserialize, Serialize};
use thiserror::Error;
use vb6parse::errors::ErrorDetails;
#[derive(Error, Debug, Clone)]
pub enum SemanticError {
UndefinedSymbol {
name: String,
location: SourceLocation,
},
DuplicateSymbol {
name: String,
location: SourceLocation,
previous_location: SourceLocation,
},
TypeMismatch {
expected: String,
found: String,
location: SourceLocation,
},
InvalidScope {
message: String,
},
InvalidType {
message: String,
location: SourceLocation,
},
CircularDependency {
message: String,
},
InvalidOperation {
message: String,
location: SourceLocation,
},
InaccessibleSymbol {
name: String,
visibility: String,
location: SourceLocation,
},
InvalidAssignment {
message: String,
location: SourceLocation,
},
ParameterMismatch {
message: String,
location: SourceLocation,
},
FileReadError {
file: String,
message: String,
},
FileParseError {
file: String,
diagnostics: Vec<ErrorDetails<'static>>,
},
AnalysisError(String),
}
impl std::fmt::Display for SemanticError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SemanticError::UndefinedSymbol { name, location } => {
write!(f, "Undefined symbol: {name} at {location}")
}
SemanticError::DuplicateSymbol {
name,
location,
previous_location,
} => write!(
f,
"Symbol already defined: {name} at {location}, previously defined at {previous_location}"
),
SemanticError::TypeMismatch {
expected,
found,
location,
} => write!(
f,
"Type mismatch: expected {expected}, found {found} at {location}"
),
SemanticError::InvalidScope { message } => write!(f, "Invalid scope: {message}"),
SemanticError::InvalidType { message, location } => {
write!(f, "Invalid type: {message} at {location}")
}
SemanticError::CircularDependency { message } => {
write!(f, "Circular dependency detected: {message}")
}
SemanticError::InvalidOperation { message, location } => {
write!(f, "Invalid operation: {message} at {location}")
}
SemanticError::InaccessibleSymbol {
name,
visibility,
location,
} => write!(
f,
"Inaccessible symbol: {name} is {visibility} at {location}"
),
SemanticError::InvalidAssignment { message, location } => {
write!(f, "Invalid assignment: {message} at {location}")
}
SemanticError::ParameterMismatch { message, location } => {
write!(f, "Parameter mismatch: {message} at {location}")
}
SemanticError::FileReadError { file, message } => {
write!(f, "Failed to read file {file}: {message}")
}
SemanticError::FileParseError { file, diagnostics } => {
write!(f, "Failed to parse file {file}")?;
for diagnostic in diagnostics {
match diagnostic.print_to_string() {
Ok(text) => write!(f, "\n{text}")?,
Err(_) => write!(f, "\n{diagnostic:?}")?,
}
}
Ok(())
}
SemanticError::AnalysisError(message) => write!(f, "Analysis error: {message}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceLocation {
pub file: String,
pub line: usize,
pub column: usize,
}
impl std::fmt::Display for SourceLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.file, self.line, self.column)
}
}
pub type Result<T> = std::result::Result<T, SemanticError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_read_error_formats_file_and_source() {
let error = SemanticError::FileReadError {
file: "module.bas".to_string(),
message: "permission denied".to_string(),
};
let message = error.to_string();
assert!(message.contains("module.bas"));
assert!(message.contains("permission denied"));
}
#[test]
fn file_parse_error_pretty_prints_source_diagnostics() {
let error = SemanticError::FileParseError {
file: "module.bas".to_string(),
diagnostics: vec![ErrorDetails {
source_name: "module.bas".to_string().into_boxed_str(),
source_content: "Dim x As ?",
error_offset: 8,
line_start: 1,
line_end: 1,
kind: Box::new(vb6parse::errors::ErrorKind::Lexer(
vb6parse::errors::LexerError::UnknownToken {
token: "?".to_string(),
},
)),
severity: vb6parse::errors::Severity::Error,
labels: vec![],
notes: vec![],
}],
};
let message = error.to_string();
assert!(message.contains("module.bas"));
assert!(message.contains("error here"));
assert!(
!message.contains("ErrorDetails {"),
"diagnostics should be rendered with the source display, not Debug"
);
}
}