You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
3.8 KiB
Rust
146 lines
3.8 KiB
Rust
extern crate proc_macro;
|
|
|
|
use proc_macro::{Literal, TokenStream, TokenTree};
|
|
use proc_macro_hack::proc_macro_hack;
|
|
use proc_macro_roids::{DeriveInputExt, DeriveInputStructExt, FieldExt};
|
|
use quote::{format_ident, quote};
|
|
use std::str::FromStr;
|
|
|
|
use byteorder::{BigEndian, LittleEndian, NativeEndian};
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
use std::iter::FromIterator;
|
|
use std::mem::align_of;
|
|
use syn::parse::{Parse, ParseBuffer, Parser};
|
|
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Expr, LitStr, Token};
|
|
|
|
#[derive(Debug)]
|
|
struct UnpackInput {
|
|
pattern: String,
|
|
input: Box<Expr>,
|
|
}
|
|
|
|
impl Parse for UnpackInput {
|
|
fn parse<'a>(input: &'a ParseBuffer<'a>) -> Result<Self, syn::Error> {
|
|
let pattern = input.parse::<LitStr>()?.value().into();
|
|
input.parse::<Token![,]>()?;
|
|
|
|
Ok(UnpackInput {
|
|
pattern,
|
|
input: input.parse()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
enum Endian {
|
|
NativeEndian,
|
|
LittleEndian,
|
|
BigEndian,
|
|
}
|
|
|
|
#[proc_macro_hack]
|
|
pub fn unpack(input: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(input as UnpackInput);
|
|
if input.pattern.is_empty() {
|
|
return quote! { () }.into();
|
|
}
|
|
|
|
let (order, alignment, indic) = match input
|
|
.pattern
|
|
.chars()
|
|
.nth(0)
|
|
.expect("Can't read first letter of pattern")
|
|
{
|
|
'!' | '>' => (Endian::BigEndian, false, true),
|
|
|
|
'<' => (Endian::LittleEndian, false, true),
|
|
|
|
'=' => (Endian::NativeEndian, false, true),
|
|
|
|
'@' => (Endian::NativeEndian, true, true),
|
|
|
|
_ => (Endian::NativeEndian, true, false),
|
|
};
|
|
|
|
let rest = if indic {
|
|
input.pattern.chars().skip(1).collect()
|
|
} else {
|
|
input.pattern
|
|
};
|
|
|
|
let mut i = 0;
|
|
|
|
let x: &[u8] = &[];
|
|
|
|
let endian = match order {
|
|
Endian::BigEndian => quote! { ::packd::parser::Endian::BigEndian },
|
|
Endian::LittleEndian => quote! { ::packd::parser::Endian::LittleEndian },
|
|
Endian::NativeEndian => quote! { ::packd::parser::Endian::NativeEndian },
|
|
};
|
|
|
|
let mut parsers: Vec<_> = vec![];
|
|
let mut marked_values = vec![];
|
|
let mut offset = 0;
|
|
|
|
for char in rest.chars() {
|
|
let value = format_ident!("value_{}", i.to_string());
|
|
match char {
|
|
'x' => offset += 1,
|
|
'h' => {
|
|
marked_values.push(i);
|
|
parsers.push(quote! {
|
|
let #value = ::packd::parser::unpack_h(#endian, &input[offset..offset + 2]);
|
|
offset += 2;
|
|
});
|
|
}
|
|
|
|
'H' => {
|
|
marked_values.push(i);
|
|
parsers.push(quote! {
|
|
let #value = ::packd::parser::unpack_H(#endian, &input[offset..offset + 2]);
|
|
offset += 2;
|
|
});
|
|
|
|
if alignment {
|
|
parsers.push(quote! {
|
|
offset += 2 % ::std::mem::align_of::<u16>();
|
|
});
|
|
}
|
|
}
|
|
|
|
'l' => {
|
|
marked_values.push(i);
|
|
parsers.push(quote! {
|
|
let #value = ::packd::parser::unpack_l(#endian, &input[offset..offset + 4]);
|
|
offset += 4;
|
|
});
|
|
|
|
if alignment {
|
|
parsers.push(quote! {
|
|
offset += 2 % ::std::mem::align_of::<i32>();
|
|
});
|
|
}
|
|
}
|
|
|
|
_ => panic!("Found unrecognized format char '{}'", char),
|
|
}
|
|
i += 1;
|
|
}
|
|
|
|
let items = marked_values
|
|
.iter()
|
|
.map(|x| format_ident!("value_{}", x.to_string()))
|
|
.collect::<Vec<_>>();
|
|
|
|
let input_val = input.input;
|
|
|
|
return quote! {
|
|
{
|
|
let input = #input_val;
|
|
let mut offset = 0;
|
|
#(#parsers)*
|
|
(#(#items,)*)
|
|
}
|
|
}
|
|
.into();
|
|
}
|