use std::path::PathBuf;
use crate::ast::File;
#[derive(Debug, Clone)]
pub enum ResolveError {
ImportCycle { chain: Vec<PathBuf> },
FileNotFound { path: PathBuf },
IoError { path: PathBuf, message: String },
ParseFailed {
path: PathBuf,
errors: Vec<ParseError>,
},
}
impl std::fmt::Display for ResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResolveError::ImportCycle { chain } => {
let paths: Vec<_> = chain.iter().map(|p| p.display().to_string()).collect();
write!(f, "import cycle detected: {}", paths.join(" → "))
}
ResolveError::FileNotFound { path } => {
write!(f, "file not found: {}", path.display())
}
ResolveError::IoError { path, message } => {
write!(f, "I/O error reading {}: {}", path.display(), message)
}
ResolveError::ParseFailed { path, errors } => {
write!(
f,
"parse errors in {}: {} error(s)",
path.display(),
errors.len()
)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCode {
E0001,
E0002,
E0003,
E0004,
E0005,
E0010,
E0011,
E0012,
E0013,
E0014,
E0015,
E0016,
E0017,
E0018,
E0701,
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorCode::E0001 => write!(f, "E0001"),
ErrorCode::E0002 => write!(f, "E0002"),
ErrorCode::E0003 => write!(f, "E0003"),
ErrorCode::E0004 => write!(f, "E0004"),
ErrorCode::E0005 => write!(f, "E0005"),
ErrorCode::E0010 => write!(f, "E0010"),
ErrorCode::E0011 => write!(f, "E0011"),
ErrorCode::E0012 => write!(f, "E0012"),
ErrorCode::E0013 => write!(f, "E0013"),
ErrorCode::E0014 => write!(f, "E0014"),
ErrorCode::E0015 => write!(f, "E0015"),
ErrorCode::E0016 => write!(f, "E0016"),
ErrorCode::E0017 => write!(f, "E0017"),
ErrorCode::E0018 => write!(f, "E0018"),
ErrorCode::E0701 => write!(f, "E0701"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseResult {
pub file: File,
pub errors: Vec<ParseError>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub code: ErrorCode,
pub line: usize,
pub col: usize,
pub message: String,
pub help: Option<String>,
pub suggestion: Option<String>,
}
impl ParseError {
pub fn unexpected_token(found: String, expected: String, line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0010,
line,
col,
message: format!("unexpected token {found:?} at line {line}, col {col}; expected {expected}"),
help: None,
suggestion: None,
}
}
pub fn unexpected_eof(line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0002,
line,
col,
message: "unexpected end of file".into(),
help: None,
suggestion: None,
}
}
pub fn indent_error(line: usize, col: usize, message: String) -> Self {
Self {
code: ErrorCode::E0003,
line,
col,
message: format!("indentation error at line {line}: {message}"),
help: Some("indentation must be a multiple of 4 spaces".into()),
suggestion: None,
}
}
pub fn invalid_escape(line: usize, col: usize, message: String) -> Self {
Self {
code: ErrorCode::E0004,
line,
col,
message: format!("invalid escape at line {line}, col {col}: {message}"),
help: Some("valid escapes are: \\n, \\t, \\r, \\\\, \\\"".into()),
suggestion: None,
}
}
pub fn unterminated_string(line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0005,
line,
col,
message: format!("unterminated string at line {line}, col {col}"),
help: Some("string literals must be closed with a matching double-quote".into()),
suggestion: None,
}
}
pub fn undefined_variable(name: String, line: usize, col: usize, suggestion: Option<String>) -> Self {
let msg = if let Some(ref s) = suggestion {
format!("undefined variable '{name}' — did you mean '{s}'?")
} else {
format!("undefined variable '{name}'")
};
Self {
code: ErrorCode::E0011,
line,
col,
message: msg,
help: Some("check the variable name for typos".into()),
suggestion,
}
}
pub fn unsupported_bin_op(op: &str, left: &str, right: &str, line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0012,
line,
col,
message: format!("cannot apply '{op}' to {left} and {right}"),
help: None,
suggestion: None,
}
}
pub fn unsupported_expr(desc: &str, line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0013,
line,
col,
message: format!("unsupported expression: {desc}"),
help: None,
suggestion: None,
}
}
pub fn not_callable(value_desc: &str, line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0014,
line,
col,
message: format!("cannot call {value_desc}: value is not callable"),
help: None,
suggestion: None,
}
}
pub fn index_out_of_bounds(index: usize, len: usize, line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0015,
line,
col,
message: format!("index out of bounds: index {index} is not valid for list of length {len}"),
help: None,
suggestion: None,
}
}
pub fn type_mismatch(expected: &str, got: &str, line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0016,
line,
col,
message: format!("type mismatch: expected {expected}, got {got}"),
help: None,
suggestion: None,
}
}
pub fn missing_indented_block(line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0017,
line,
col,
message: "expected an indented block after ':'".into(),
help: Some("indent the body by 4 spaces relative to the header".into()),
suggestion: None,
}
}
pub fn missing_func_body(line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0018,
line,
col,
message: "expected function body: 'steps:' or '...'".into(),
help: Some("add a 'steps:' block or write '...' as a placeholder".into()),
suggestion: None,
}
}
pub fn internal(msg: String, line: usize, col: usize) -> Self {
Self {
code: ErrorCode::E0701,
line,
col,
message: format!("internal error: {msg}"),
help: Some("this is a bug — please report it".into()),
suggestion: None,
}
}
pub fn format_with_source(&self, source: &str) -> String {
let line_starts = crate::format::compute_line_starts(source);
crate::format::format_diagnostic_with_starts(self, source, &line_starts)
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "error[{}] at line {}, col {}: {}", self.code, self.line, self.col, self.message)?;
if let Some(ref help) = self.help {
write!(f, "\n help: {help}")?;
}
if let Some(ref suggestion) = self.suggestion {
write!(f, "\n suggestion: {suggestion}")?;
}
Ok(())
}
}
impl std::error::Error for ParseError {}