xo65 0.1.0

Library for parsing cc65 object files (xo65 format)
Documentation
use std::borrow::Cow;

use crate::xo65::Xo65Bytes;

#[derive(Debug)]
pub struct ParseError {
    msg: Cow<'static, str>,
    source: Option<Box<ParseError>>,
}

impl ParseError {
    pub(crate) fn new<S>(msg: S) -> Self
    where
        S: Into<Cow<'static, str>>,
    {
        Self {
            msg: msg.into(),
            source: None,
        }
    }
}

impl std::error::Error for ParseError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.source.as_ref().map(|source| source as _)
    }
}

impl std::fmt::Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.msg.fmt(f)
    }
}

macro_rules! parse_err {
    ($msg:expr $(,)?) => {{
        $crate::parse::ParseError::new(::std::format!($msg))
    }};
    ($fmt:expr, $($args:tt)*) => {{
        $crate::parse::ParseError::new(::std::format!($fmt, $($args)*))
    }};
}
pub(crate) use parse_err;

macro_rules! parse_bail {
    ($msg:expr $(,)?) => {{
        return Err($crate::parse::parse_err!($msg));
    }};
    ($fmt:expr, $($args:tt)*) => {{
        return Err($crate::parse::parse_err!($fmt, $($args)*));
    }};
}
pub(crate) use parse_bail;

pub(crate) type ParseResult<T> = Result<T, ParseError>;

pub(crate) trait ParseErrorContext: Sized {
    fn context(self, msg: &'static str) -> Self;

    fn with_context<F, S>(self, f: F) -> Self
    where
        F: FnOnce() -> S,
        S: Into<Cow<'static, str>>;
}

impl<T> ParseErrorContext for ParseResult<T> {
    fn context(self, msg: &'static str) -> Self {
        self.map_err(|e| ParseError {
            msg: msg.into(),
            source: Some(Box::new(e)),
        })
    }

    fn with_context<F, S>(self, f: F) -> Self
    where
        F: FnOnce() -> S,
        S: Into<Cow<'static, str>>,
    {
        self.map_err(|e| ParseError {
            msg: f().into(),
            source: Some(Box::new(e)),
        })
    }
}

pub(crate) trait ParseAt<'data>: Sized {
    fn parse_at(bytes: &Xo65Bytes<'data>, off: &mut usize) -> ParseResult<Self>;
}

impl<const LEN: usize> ParseAt<'_> for [u8; LEN] {
    fn parse_at(bytes: &Xo65Bytes<'_>, off: &mut usize) -> ParseResult<Self> {
        <&[u8; LEN]>::parse_at(bytes, off).copied()
    }
}

impl<'data, const LEN: usize> ParseAt<'data> for &'data [u8; LEN] {
    fn parse_at(bytes: &Xo65Bytes<'data>, off: &mut usize) -> ParseResult<Self> {
        let bytes = bytes.get(*off..)?;
        let buf = bytes.first_chunk::<LEN>()?;

        *off += LEN;

        Ok(buf)
    }
}

macro_rules! impl_parse_at_for_int {
    ($($ty:ty)*) => {
        $(
            impl ParseAt<'_> for $ty {
                fn parse_at(bytes: &Xo65Bytes<'_>, off: &mut usize) -> ParseResult<Self> {
                    const SIZE: usize = std::mem::size_of::<$ty>();

                    let res = <[u8; SIZE]>::parse_at(bytes, off)?;
                    let res = Self::from_le_bytes(res);

                    Ok(res)
                }
            }
        )*
    }
}

impl_parse_at_for_int!(i8 i16 i32 u8 u16 u32);

pub(crate) fn parse_bytes_at<'data>(
    bytes: &Xo65Bytes<'data>,
    off: &mut usize,
    len: usize,
) -> ParseResult<&'data [u8]> {
    let res = bytes.get(*off..*off + len)?.as_inner();

    *off += len;

    Ok(res)
}

pub(crate) fn parse_uleb128_at(bytes: &Xo65Bytes<'_>, off: &mut usize) -> ParseResult<u32> {
    let mut res: u32 = 0;
    let mut coef: u32 = 1;

    loop {
        let b = u8::parse_at(bytes, off)?;

        res |= u32::from(b & 0x7F)
            .checked_mul(coef)
            .ok_or_else(|| parse_err!("parse_uleb128_at: integer overflow"))?;

        if (b & 0x80) == 0 {
            break;
        }

        coef = coef
            .checked_mul(0x80)
            .ok_or_else(|| parse_err!("parse_uleb128_at: integer overflow"))?
    }

    Ok(res)
}