Split error enum into Encode vs. Decode errors

This commit is contained in:
2026-03-10 00:08:51 -07:00
parent 93339881c5
commit d73faf64c7
4 changed files with 46 additions and 40 deletions
+5 -5
View File
@@ -1,10 +1,10 @@
use codepage_437::CP437_WINGDINGS; use codepage_437::CP437_WINGDINGS;
use super::error::ParseError; use super::error::EncodeError;
/// Serialize a multi-line code string for an object or a scroll. /// Serialize a multi-line code string for an object or a scroll.
/// Uses ZZT's convention of CR-terminated lines. /// Uses ZZT's convention of CR-terminated lines.
pub fn encode_multiline(input: &str) -> Result<Vec<u8>, ParseError> { pub fn encode_multiline(input: &str) -> Result<Vec<u8>, EncodeError> {
input input
.chars() .chars()
.map(|c| { .map(|c| {
@@ -13,7 +13,7 @@ pub fn encode_multiline(input: &str) -> Result<Vec<u8>, ParseError> {
} else { } else {
CP437_WINGDINGS CP437_WINGDINGS
.encode(c) .encode(c)
.ok_or_else(|| ParseError::EncodingError(c)) .ok_or_else(|| EncodeError::EncodingError(c))
} }
}) })
.collect() .collect()
@@ -35,13 +35,13 @@ pub fn decode_multiline(input: &[u8]) -> String {
/// Serialize a single-line string for a board title. /// Serialize a single-line string for a board title.
/// Newlines cannot be encoded because ZZT interprets them as dingbats (♪). /// Newlines cannot be encoded because ZZT interprets them as dingbats (♪).
pub fn encode_oneline(input: &str) -> Result<Vec<u8>, ParseError> { pub fn encode_oneline(input: &str) -> Result<Vec<u8>, EncodeError> {
input input
.chars() .chars()
.map(|c| { .map(|c| {
CP437_WINGDINGS CP437_WINGDINGS
.encode(c) .encode(c)
.ok_or_else(|| ParseError::EncodingError(c)) .ok_or_else(|| EncodeError::EncodingError(c))
}) })
.collect() .collect()
} }
+19 -13
View File
@@ -2,7 +2,7 @@ use nom::error::{ErrorKind, ParseError as NomParseError};
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ParseError { pub enum DecodeError {
#[error("invalid ZZT file magic")] #[error("invalid ZZT file magic")]
InvalidMagic, InvalidMagic,
@@ -12,20 +12,11 @@ pub enum ParseError {
#[error("negative stat count")] #[error("negative stat count")]
NegativeStatCount, NegativeStatCount,
#[error("string too long")]
StringTooLong { max: u8 },
#[error("cannot encode character: {0}")]
EncodingError(char),
#[error("board data too large")]
BoardTooLarge,
#[error("nom error: {0}")] #[error("nom error: {0}")]
NomError(String), NomError(String),
} }
impl<I> NomParseError<I> for ParseError { impl<I> NomParseError<I> for DecodeError {
fn from_error_kind(_input: I, kind: ErrorKind) -> Self { fn from_error_kind(_input: I, kind: ErrorKind) -> Self {
Self::NomError(kind.description().to_string()) Self::NomError(kind.description().to_string())
} }
@@ -35,11 +26,26 @@ impl<I> NomParseError<I> for ParseError {
} }
} }
impl From<nom::Err<ParseError>> for ParseError { impl From<nom::Err<DecodeError>> for DecodeError {
fn from(value: nom::Err<ParseError>) -> Self { fn from(value: nom::Err<DecodeError>) -> Self {
match value { match value {
nom::Err::Error(e) | nom::Err::Failure(e) => e, nom::Err::Error(e) | nom::Err::Failure(e) => e,
nom::Err::Incomplete(needed) => Self::NomError(format!("incomplete: {:?}", needed)), nom::Err::Incomplete(needed) => Self::NomError(format!("incomplete: {:?}", needed)),
} }
} }
} }
#[derive(Debug, Error)]
pub enum EncodeError {
#[error("invalid tile count: {0}")]
InvalidTileCount(usize),
#[error("string too long")]
StringTooLong { max: u8 },
#[error("cannot encode character: {0}")]
EncodingError(char),
#[error("board data too large")]
BoardTooLarge,
}
+1 -1
View File
@@ -5,5 +5,5 @@ mod parse;
pub use elements::Element; pub use elements::Element;
pub use encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline}; pub use encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
pub use error::ParseError; pub use error::{DecodeError, EncodeError};
pub use parse::{Board, Program, Stat, Tile, World}; pub use parse::{Board, Program, Stat, Tile, World};
+21 -21
View File
@@ -9,7 +9,7 @@ use nom::{
use super::elements::Element; use super::elements::Element;
use super::encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline}; use super::encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
use super::error::ParseError; use super::error::{DecodeError, EncodeError};
/// A single tile on a ZZT board. /// A single tile on a ZZT board.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@@ -161,13 +161,13 @@ impl Default for World {
// Parsing helpers // Parsing helpers
fn bool_u8(input: &[u8]) -> IResult<&[u8], bool, ParseError> { fn bool_u8(input: &[u8]) -> IResult<&[u8], bool, DecodeError> {
let (input, byte) = le_u8(input)?; let (input, byte) = le_u8(input)?;
Ok((input, byte != 0)) Ok((input, byte != 0))
} }
fn pstring(cap: u8) -> impl Fn(&[u8]) -> IResult<&[u8], String, ParseError> { fn pstring(cap: u8) -> impl Fn(&[u8]) -> IResult<&[u8], String, DecodeError> {
move |input: &[u8]| -> IResult<&[u8], String, ParseError> { move |input: &[u8]| -> IResult<&[u8], String, DecodeError> {
let (input, len) = le_u8(input)?; let (input, len) = le_u8(input)?;
let actual_len = len.min(cap); let actual_len = len.min(cap);
let (input, data) = take(actual_len)(input)?; let (input, data) = take(actual_len)(input)?;
@@ -176,7 +176,7 @@ fn pstring(cap: u8) -> impl Fn(&[u8]) -> IResult<&[u8], String, ParseError> {
} }
} }
fn board_slice(bytes: &[u8]) -> IResult<&[u8], &[u8], ParseError> { fn board_slice(bytes: &[u8]) -> IResult<&[u8], &[u8], DecodeError> {
let (_, size) = le_u16.parse(bytes)?; let (_, size) = le_u16.parse(bytes)?;
take(size as usize + 2).parse(bytes) take(size as usize + 2).parse(bytes)
} }
@@ -186,7 +186,7 @@ fn board_slice(bytes: &[u8]) -> IResult<&[u8], &[u8], ParseError> {
trait SerializationHelpers { trait SerializationHelpers {
fn push_bool(&mut self, value: bool); fn push_bool(&mut self, value: bool);
fn push_i16(&mut self, value: i16); fn push_i16(&mut self, value: i16);
fn push_string(&mut self, cap: u8, value: &str) -> Result<(), ParseError>; fn push_string(&mut self, cap: u8, value: &str) -> Result<(), EncodeError>;
fn push_padding(&mut self, size: usize); fn push_padding(&mut self, size: usize);
} }
@@ -199,10 +199,10 @@ impl SerializationHelpers for Vec<u8> {
self.extend(value.to_le_bytes()); self.extend(value.to_le_bytes());
} }
fn push_string(&mut self, cap: u8, value: &str) -> Result<(), ParseError> { fn push_string(&mut self, cap: u8, value: &str) -> Result<(), EncodeError> {
let bytes = encode_oneline(value)?; let bytes = encode_oneline(value)?;
if bytes.len() > cap as usize { if bytes.len() > cap as usize {
return Err(ParseError::StringTooLong { max: cap }); return Err(EncodeError::StringTooLong { max: cap });
} }
self.push(bytes.len() as u8); self.push(bytes.len() as u8);
self.extend_from_slice(&bytes); self.extend_from_slice(&bytes);
@@ -217,10 +217,10 @@ impl SerializationHelpers for Vec<u8> {
impl World { impl World {
/// Parse a ZZT world from bytes. /// Parse a ZZT world from bytes.
pub fn from_bytes(bytes: &[u8]) -> Result<World, ParseError> { pub fn from_bytes(bytes: &[u8]) -> Result<World, DecodeError> {
let (input, _) = tag(&[0xff, 0xff][..]) let (input, _) = tag(&[0xff, 0xff][..])
.parse(bytes) .parse(bytes)
.map_err(|_: nom::Err<ParseError>| ParseError::InvalidMagic)?; .map_err(|_: nom::Err<DecodeError>| DecodeError::InvalidMagic)?;
let (input, num_boards) = le_i16.parse(input)?; let (input, num_boards) = le_i16.parse(input)?;
let (input, (ammo, gems, keys)) = (le_i16, le_i16, count(bool_u8, 7)).parse(input)?; let (input, (ammo, gems, keys)) = (le_i16, le_i16, count(bool_u8, 7)).parse(input)?;
let (input, (health, starting_board, torches, torch_cycles, energizer_cycles)) = let (input, (health, starting_board, torches, torch_cycles, energizer_cycles)) =
@@ -235,7 +235,7 @@ impl World {
// Load boards // Load boards
let num_boards = num_boards as usize + 1; let num_boards = num_boards as usize + 1;
let (_input, chunks) = count(board_slice, num_boards).parse(input)?; let (_input, chunks) = count(board_slice, num_boards).parse(input)?;
let boards: Result<Vec<Board>, ParseError> = chunks let boards: Result<Vec<Board>, DecodeError> = chunks
.iter() .iter()
.map(|bytes: &&[u8]| Board::from_bytes(bytes)) .map(|bytes: &&[u8]| Board::from_bytes(bytes))
.collect(); .collect();
@@ -261,7 +261,7 @@ impl World {
} }
/// Serialize this world to bytes. /// Serialize this world to bytes.
pub fn to_bytes(&self) -> Result<Vec<u8>, ParseError> { pub fn to_bytes(&self) -> Result<Vec<u8>, EncodeError> {
let mut result = Vec::with_capacity(512); let mut result = Vec::with_capacity(512);
result.push_i16(-1); // file magic: ZZT world result.push_i16(-1); // file magic: ZZT world
result.push_i16(self.boards.len() as i16 - 1); result.push_i16(self.boards.len() as i16 - 1);
@@ -295,7 +295,7 @@ impl World {
impl Board { impl Board {
/// Parse a board from bytes (including the 2-byte size header). /// Parse a board from bytes (including the 2-byte size header).
pub fn from_bytes(bytes: &[u8]) -> Result<Board, ParseError> { pub fn from_bytes(bytes: &[u8]) -> Result<Board, DecodeError> {
// Ignore length bytes // Ignore length bytes
let (input, _) = le_u16.parse(bytes)?; let (input, _) = le_u16.parse(bytes)?;
@@ -313,7 +313,7 @@ impl Board {
for _ in 0..count { for _ in 0..count {
tiles.push(Tile { element, color }); tiles.push(Tile { element, color });
if tiles.len() > NUM_TILES { if tiles.len() > NUM_TILES {
return Err(ParseError::InvalidTileCount(tiles.len())); return Err(DecodeError::InvalidTileCount(tiles.len()));
} }
} }
} }
@@ -334,7 +334,7 @@ impl Board {
let (input, num_stats) = le_i16(input)?; let (input, num_stats) = le_i16(input)?;
let num_stats = num_stats + 1; let num_stats = num_stats + 1;
if num_stats < 0 { if num_stats < 0 {
return Err(ParseError::NegativeStatCount); return Err(DecodeError::NegativeStatCount);
} }
let (_input, stats) = count(Stat::parse, num_stats as usize).parse(input)?; let (_input, stats) = count(Stat::parse, num_stats as usize).parse(input)?;
@@ -357,19 +357,19 @@ impl Board {
} }
/// Parse a standalone .brd file (same format, no world header). /// Parse a standalone .brd file (same format, no world header).
pub fn from_brd_bytes(bytes: &[u8]) -> Result<Board, ParseError> { pub fn from_brd_bytes(bytes: &[u8]) -> Result<Board, DecodeError> {
Board::from_bytes(bytes) Board::from_bytes(bytes)
} }
/// Serialize this board to bytes. /// Serialize this board to bytes.
pub fn to_bytes(&self) -> Result<Vec<u8>, ParseError> { pub fn to_bytes(&self) -> Result<Vec<u8>, EncodeError> {
let mut result = vec![]; let mut result = vec![];
result.push_padding(2); // reserve space for board size result.push_padding(2); // reserve space for board size
result.push_string(50, &self.name)?; result.push_string(50, &self.name)?;
// Encode terrain // Encode terrain
if self.tiles.len() != 1500 { if self.tiles.len() != 1500 {
return Err(ParseError::InvalidTileCount(self.tiles.len())); return Err(EncodeError::InvalidTileCount(self.tiles.len()));
} }
let mut iter = self.tiles.iter().peekable(); let mut iter = self.tiles.iter().peekable();
while let Some(tile) = iter.next() { while let Some(tile) = iter.next() {
@@ -407,7 +407,7 @@ impl Board {
// Fix up board size // Fix up board size
let size: u16 = (result.len() - 2) let size: u16 = (result.len() - 2)
.try_into() .try_into()
.map_err(|_| ParseError::BoardTooLarge)?; .map_err(|_| EncodeError::BoardTooLarge)?;
result.splice(0..2, size.to_le_bytes()); result.splice(0..2, size.to_le_bytes());
Ok(result) Ok(result)
@@ -415,7 +415,7 @@ impl Board {
} }
impl Stat { impl Stat {
fn parse(input: &[u8]) -> IResult<&[u8], Self, ParseError> { fn parse(input: &[u8]) -> IResult<&[u8], Self, DecodeError> {
let (input, (x, y, x_step, y_step)) = (le_u8, le_u8, le_i16, le_i16).parse(input)?; let (input, (x, y, x_step, y_step)) = (le_u8, le_u8, le_i16, le_i16).parse(input)?;
let (input, (cycle, p1, p2, p3)) = (le_i16, le_u8, le_u8, le_u8).parse(input)?; let (input, (cycle, p1, p2, p3)) = (le_i16, le_u8, le_u8, le_u8).parse(input)?;
let (input, (follower, leader)) = (le_i16, le_i16).parse(input)?; let (input, (follower, leader)) = (le_i16, le_i16).parse(input)?;
@@ -459,7 +459,7 @@ impl Stat {
)) ))
} }
fn to_bytes(&self) -> Result<Vec<u8>, ParseError> { fn to_bytes(&self) -> Result<Vec<u8>, EncodeError> {
let mut result = vec![]; let mut result = vec![];
result.push(self.x); result.push(self.x);
result.push(self.y); result.push(self.y);