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)]
enum ProgramErrorKind {
InvalidSyntax,
SyntaxError(SyntaxError),
IllegalMemoryAccess(isize),
ConversionError,
EndOfFile,
@ -73,7 +85,7 @@ enum ProgramErrorKind {
impl fmt::Display for ProgramErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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::IllegalMemoryAccess(loc) => write!(f, "illegal memory access @ {loc}"),
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 {
fn from(value: TryFromIntError) -> Self {
Self::new(ProgramErrorKind::ConversionError, value)
@ -131,31 +152,37 @@ struct Program {
}
impl Program {
fn word_length(mut word: impl Iterator<Item = u8>) -> Result<isize> {
let result = word.try_fold((0isize, true, true), |(count, was_w, pos), byte| {
fn word_length(mut word: impl Iterator<Item = (usize, u8)>) -> Result<isize> {
let result = word.try_fold((0isize, true, true, 0usize), |(count, was_w, pos, _), (i, byte)| {
match (byte, was_w, pos) {
(b'a', true, true) => Some((count, false, true)),
(b'w', false, true) => Some((count + 1, true, true)),
(b'!', false, true) => Some((count, false, false)),
_ => None,
(b'a', true, true) => Ok((count, false, true, i)),
(b'w', false, true) => Ok((count + 1, true, true, i)),
(b'!', false, true) => Ok((count, false, false, i)),
// 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 {
Some((_, _, false)) => -1,
Ok((_, _, false, _)) => -1,
_ => 1,
};
match result {
Some((count, false, _)) => Ok(count * sign),
_ => Err(ProgramError::from(ProgramErrorKind::InvalidSyntax)),
Ok((count, false, _, _)) => Ok(count * sign),
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
.skip_while(|ch| *ch != b'a' && *ch != b'w' && *ch != b'!')
.take_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'!')
.peekable();
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();
loop {
@ -184,7 +211,7 @@ impl Program {
pub fn from_program(program: impl Iterator<Item = u8>) -> Result<Self> {
Ok(Self {
memory: Self::parse(program)?,
memory: Self::parse(program.enumerate())?,
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> {
match self.memory.get(n) {
Some(n) => Ok(n),
None => Err(ProgramError::from(ProgramErrorKind::IllegalMemoryAccess(
n as isize,
))),
None => Err(ProgramErrorKind::IllegalMemoryAccess(n as isize).into()),
}
}