use std::fmt;
use std::path::PathBuf;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScriptError {
pub file: PathBuf,
pub line: u32,
pub column: u32,
pub offset: u64,
pub message: String,
}
impl ScriptError {
pub fn new(
file: impl Into<PathBuf>,
line: u32,
column: u32,
offset: u64,
message: impl Into<String>,
) -> Self {
Self {
file: file.into(),
line,
column,
offset,
message: message.into(),
}
}
}
impl fmt::Display for ScriptError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.line == 0 {
write!(f, "{}: {}", self.file.display(), self.message)
} else {
write!(
f,
"{}:{}:{}: {}",
self.file.display(),
self.line,
self.column,
self.message
)
}
}
}
impl std::error::Error for ScriptError {}
impl From<Box<ScriptError>> for ScriptError {
fn from(error: Box<ScriptError>) -> Self {
*error
}
}
impl From<ScriptError> for crate::Error {
fn from(error: ScriptError) -> Self {
crate::Error::Script(Box::new(error))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EvalError {
UndefinedSymbol(Vec<u8>),
UndefinedSection(Vec<u8>),
UndefinedRegion(Vec<u8>),
UnknownConstant(Vec<u8>),
DivisionByZero,
NoLocationCounter,
DotBackwards {
from: u64,
to: u64,
},
AssertionFailed(Vec<u8>),
NotYetKnown(String),
Other(String),
}
impl fmt::Display for EvalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let lossy = |bytes: &[u8]| String::from_utf8_lossy(bytes).into_owned();
match self {
Self::UndefinedSymbol(name) => write!(
f,
"undefined symbol `{}' referenced in expression",
lossy(name)
),
Self::UndefinedSection(name) => write!(
f,
"undefined section `{}' referenced in expression",
lossy(name)
),
Self::UndefinedRegion(name) => write!(
f,
"undefined MEMORY region `{}' referenced in expression",
lossy(name)
),
Self::UnknownConstant(name) => write!(
f,
"unknown constant `{}' referenced in expression",
lossy(name)
),
Self::DivisionByZero => f.write_str("division by zero"),
Self::NoLocationCounter => f.write_str("location counter used where it has no value"),
Self::DotBackwards { from, to } => write!(
f,
"cannot move location counter backwards (from {from:#x} to {to:#x})"
),
Self::AssertionFailed(message) => f.write_str(&lossy(message)),
Self::NotYetKnown(what) => write!(f, "{what} is not known yet"),
Self::Other(message) => f.write_str(message),
}
}
}
impl std::error::Error for EvalError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_and_conversion() {
let error = ScriptError::new("a.ld", 3, 7, 42, "syntax error");
assert_eq!(error.to_string(), "a.ld:3:7: syntax error");
let converted: crate::Error = error.into();
assert!(matches!(converted, crate::Error::Script(_)));
assert_eq!(converted.to_string(), "a.ld:3:7: syntax error");
let error = ScriptError::new("b.ld", 0, 0, 0, "cannot open");
assert_eq!(error.to_string(), "b.ld: cannot open");
assert!(
crate::Error::from(error)
.to_string()
.contains("cannot open")
);
}
#[test]
fn eval_error_messages() {
assert_eq!(
EvalError::UndefinedSymbol(b"foo".to_vec()).to_string(),
"undefined symbol `foo' referenced in expression"
);
assert!(
EvalError::DotBackwards { from: 16, to: 8 }
.to_string()
.contains("0x10")
);
}
}