hex-magic 0.0.1

Macros for working with bytes and hexadecimal values.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::parse::{Parse, ParseStream};

use syn::{
    spanned::Spanned,
    token::{Colon, Underscore},
    Attribute, Expr, Ident, Member, Result, Token,
};

use super::byte_pattern::BytePattern;
use super::{BUFFER_UUID, READER_UUID, VALUE_UUID};

#[derive(Debug)]
pub enum HexStructField {
    Field {
        attrs: Vec<Attribute>,
        member: Member,
        colon: Colon,
        buffer_ident: Option<Ident>,
        byte_pattern: BytePattern,
        expr: Option<Expr>,
    },
    Match {
        underscore: Underscore,
        colon: Colon,
        byte_pattern: BytePattern,
    },
}

impl HexStructField {
    fn byte_pattern(&self) -> &BytePattern {
        match self {
            Self::Field { byte_pattern, .. } | Self::Match { byte_pattern, .. } => byte_pattern,
        }
    }
    fn reader_ident(&self) -> Ident {
        Ident::new(READER_UUID, self.byte_pattern().span())
    }
    fn value_ident(&self) -> Ident {
        Ident::new(VALUE_UUID, self.byte_pattern().span())
    }
    fn buffer_ident(&self) -> Ident {
        match self {
            Self::Field {
                buffer_ident: Some(ident),
                ..
            } => ident.to_owned(),
            _ => Ident::new(BUFFER_UUID, self.byte_pattern().span()),
        }
    }
}

impl Spanned for HexStructField {
    fn span(&self) -> Span {
        match self {
            Self::Field {
                member,
                expr: Some(expr),
                ..
            } => member
                .span()
                .join(expr.span())
                .unwrap_or_else(|| member.span()),
            Self::Field {
                member,
                byte_pattern,
                ..
            } => member
                .span()
                .join(byte_pattern.span())
                .unwrap_or_else(|| member.span()),
            Self::Match {
                underscore,
                byte_pattern,
                ..
            } => underscore
                .span()
                .join(byte_pattern.span())
                .unwrap_or_else(|| underscore.span()),
        }
    }
}

impl HexStructField {
    pub fn to_tokens(&self, next: Option<&&HexStructField>) -> TokenStream {
        match self {
            Self::Match { byte_pattern, .. } => {
                let reader = self.reader_ident();
                let len = byte_pattern.len();
                let byte_pattern_string = format!("{}", byte_pattern);

                quote!(
                    {
                        let mut buf: [u8; #len] = [0; #len];
                        #reader.read(&mut buf)?;

                        #[allow(dead_code)]
                        match buf {
                            #byte_pattern => (),
                            _ => return Err(std::io::Error::new(
                                    std::io::ErrorKind::InvalidData,
                                    format!("expected {}, got {:02X?}", #byte_pattern_string, buf),
                                ))
                        }
                    }
                )
            }
            Self::Field {
                attrs,
                member,
                colon,
                byte_pattern,
                expr,
                ..
            } => {
                let reader_ident = self.reader_ident();
                let buffer_ident = self.buffer_ident();

                let match_insert = match next {
                    Some(next @ HexStructField::Match { .. }) => next.to_tokens(None),
                    _ => quote!(),
                };
                let len = byte_pattern.len();
                let byte_pattern_string = format!("{}", byte_pattern);

                let value_ident = self.value_ident();
                let value = match expr {
                    Some(expr) => quote!(#expr),
                    None => quote!(#buffer_ident),
                };

                quote!(
                    #(#attrs)*
                    #member#colon {
                        #[allow(non_snake_case)]
                        let mut #buffer_ident: [u8; #len] = [0; #len];
                        #reader_ident.read(&mut #buffer_ident)?;
                        #[allow(dead_code)]
                        match #buffer_ident {
                            #byte_pattern => (),
                            _ => return Err(std::io::Error::new(
                                    std::io::ErrorKind::InvalidData,
                                    format!("expected {}, got {:02X?}", #byte_pattern_string, #buffer_ident),
                                ))
                        }

                        #[allow(non_snake_case)]
                        let #value_ident = #value;
                        #match_insert
                        #value_ident
                    },
                )
            }
        }
    }
}

impl Parse for HexStructField {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(if input.peek(Token![_]) {
            HexStructField::Match {
                underscore: input.parse()?,
                colon: input.parse()?,
                byte_pattern: {
                    if input.peek(Ident) {
                        return Err(
                            input.error("binding of `_` match-only byte patterns is not allowed")
                        );
                    }
                    let pattern = input.parse()?;
                    if input.peek(Token![=>]) {
                        return Err(
                            input.error("binding of `_` match-only byte patterns is not allowed")
                        );
                    }
                    pattern
                },
            }
        } else {
            let attrs = Attribute::parse_inner(input)?;
            let member = input.parse()?;
            let colon = input.parse()?;
            let buffer_ident = if input.peek(Ident) {
                let ident = input.parse()?;
                input.parse::<Token![@]>()?;
                Some(ident)
            } else {
                None
            };

            let byte_pattern = input.parse()?;

            let expr = if buffer_ident.is_some() {
                input.parse::<Token![=>]>()?;
                Some(input.parse()?)
            } else {
                None
            };

            HexStructField::Field {
                attrs,
                member,
                colon,
                buffer_ident,
                byte_pattern,
                expr,
            }
        })
    }
}