add better error messages for syntax issues

This commit is contained in:
Rowan 2025-01-05 11:12:50 -06:00
parent 250e878c0e
commit 7010268d94

View file

@ -62,9 +62,21 @@ impl<T: Read> Iterator for Tape<T> {
} }
} }
#[derive(Debug)]
struct SyntaxError {
position: usize,
message: String,
}
impl SyntaxError {
pub fn new(position: usize, message: String) -> Self {
Self { position, message }
}
}
#[derive(Debug)] #[derive(Debug)]
enum ProgramErrorKind { enum ProgramErrorKind {
InvalidSyntax, SyntaxError(SyntaxError),
IllegalMemoryAccess(isize), IllegalMemoryAccess(isize),
ConversionError, ConversionError,
EndOfFile, EndOfFile,
@ -73,7 +85,7 @@ enum ProgramErrorKind {
impl fmt::Display for ProgramErrorKind { impl fmt::Display for ProgramErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::InvalidSyntax => write!(f, "invalid syntax"), Self::SyntaxError(e) => write!(f, "invalid syntax at {}: {}", e.position, e.message),
Self::EndOfFile => write!(f, "end of file"), Self::EndOfFile => write!(f, "end of file"),
Self::IllegalMemoryAccess(loc) => write!(f, "illegal memory access @ {loc}"), Self::IllegalMemoryAccess(loc) => write!(f, "illegal memory access @ {loc}"),
Self::ConversionError => todo!(), Self::ConversionError => todo!(),
@ -102,6 +114,15 @@ impl From<ProgramErrorKind> for ProgramError {
} }
} }
impl From<SyntaxError> for ProgramError {
fn from(value: SyntaxError) -> Self {
Self {
kind: ProgramErrorKind::SyntaxError(value),
source: None,
}
}
}
impl From<TryFromIntError> for ProgramError { impl From<TryFromIntError> for ProgramError {
fn from(value: TryFromIntError) -> Self { fn from(value: TryFromIntError) -> Self {
Self::new(ProgramErrorKind::ConversionError, value) Self::new(ProgramErrorKind::ConversionError, value)
@ -131,31 +152,37 @@ struct Program {
} }
impl Program { impl Program {
fn word_length(mut word: impl Iterator<Item = u8>) -> Result<isize> { fn word_length(mut word: impl Iterator<Item = (usize, u8)>) -> Result<isize> {
let result = word.try_fold((0isize, true, true), |(count, was_w, pos), byte| { let result = word.try_fold((0isize, true, true, 0usize), |(count, was_w, pos, _), (i, byte)| {
match (byte, was_w, pos) { match (byte, was_w, pos) {
(b'a', true, true) => Some((count, false, true)), (b'a', true, true) => Ok((count, false, true, i)),
(b'w', false, true) => Some((count + 1, true, true)), (b'w', false, true) => Ok((count + 1, true, true, i)),
(b'!', false, true) => Some((count, false, false)), (b'!', false, true) => Ok((count, false, false, i)),
_ => None, // errors
(b'a', false, true) => Err(SyntaxError::new(i, r#"unexpected "a". expected "w", "!" if continuing a word, or anything else to end it"#.to_string())),
(b'w', true, true) => Err(SyntaxError::new(i, r#"unexpected "w". expected "a""#.to_string())),
(b'!', true, _) | (b'!', _, false)=> Err(SyntaxError::new(i, r#"unexpected "!": may only appear at the end of a word"#.to_string())),
(v, _, false) => Err(SyntaxError::new(i, format!("unexpected {v}: whitespace or any non-word character should follow"))),
(v, _, _) => Err(SyntaxError::new(i, format!("unexpected {v}"))),
} }
}); });
let sign = match result { let sign = match result {
Some((_, _, false)) => -1, Ok((_, _, false, _)) => -1,
_ => 1, _ => 1,
}; };
match result { match result {
Some((count, false, _)) => Ok(count * sign), Ok((count, false, _, _)) => Ok(count * sign),
_ => Err(ProgramError::from(ProgramErrorKind::InvalidSyntax)), Ok((_, true, _, i)) => Err(SyntaxError::new(i, r#""#.to_string()).into()),
Err(e) => Err(e.into()),
} }
} }
fn next_word(program: impl Iterator<Item = u8>) -> Result<isize> { fn next_word(program: impl Iterator<Item = (usize, u8)>) -> Result<isize> {
let mut word = program let mut word = program
.skip_while(|ch| *ch != b'a' && *ch != b'w' && *ch != b'!') .skip_while(|(_, ch)| *ch != b'a' && *ch != b'w' && *ch != b'!')
.take_while(|ch| *ch == b'a' || *ch == b'w' || *ch == b'!') .take_while(|(_, ch)| *ch == b'a' || *ch == b'w' || *ch == b'!')
.peekable(); .peekable();
if word.peek().is_none() { if word.peek().is_none() {
@ -165,7 +192,7 @@ impl Program {
} }
} }
fn parse(mut program: impl Iterator<Item = u8>) -> Result<Vec<isize>> { fn parse(mut program: impl Iterator<Item = (usize, u8)>) -> Result<Vec<isize>> {
let mut memory = Vec::new(); let mut memory = Vec::new();
loop { loop {
@ -184,7 +211,7 @@ impl Program {
pub fn from_program(program: impl Iterator<Item = u8>) -> Result<Self> { pub fn from_program(program: impl Iterator<Item = u8>) -> Result<Self> {
Ok(Self { Ok(Self {
memory: Self::parse(program)?, memory: Self::parse(program.enumerate())?,
index: 0, index: 0,
}) })
} }
@ -217,26 +244,10 @@ impl Program {
} }
} }
fn set_index(&mut self, value: isize) -> Result<()> {
if value < 0 {
Err(ProgramErrorKind::IllegalMemoryAccess(value).into())
} else {
match usize::try_from(value) {
Ok(value) => {
self.index = value;
Ok(())
}
Err(e) => Err(ProgramError::new(ProgramErrorKind::ConversionError, e)),
}
}
}
fn get(&self, n: usize) -> Result<&isize> { fn get(&self, n: usize) -> Result<&isize> {
match self.memory.get(n) { match self.memory.get(n) {
Some(n) => Ok(n), Some(n) => Ok(n),
None => Err(ProgramError::from(ProgramErrorKind::IllegalMemoryAccess( None => Err(ProgramErrorKind::IllegalMemoryAccess(n as isize).into()),
n as isize,
))),
} }
} }