Initial proof of concept
commit
e5bc91f8bb
@ -0,0 +1,9 @@
|
||||
/target
|
||||
|
||||
|
||||
#Added by cargo
|
||||
#
|
||||
#already existing elements were commented out
|
||||
|
||||
#/target
|
||||
Cargo.lock
|
@ -0,0 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"packd",
|
||||
"packd-proc-macro",
|
||||
"packd-tests"
|
||||
]
|
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "packd-proc-macro"
|
||||
version = "0.1.0"
|
||||
authors = ["eater <=@eater.me>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro-hack = "0.5"
|
||||
proc_macro_roids = "0.7.0"
|
||||
proc-macro2 = "1.0.9"
|
||||
syn = "1.0.16"
|
||||
quote = "1.0.3"
|
||||
byteorder = "1.3.4"
|
@ -0,0 +1,145 @@
|
||||
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();
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "packd-tests"
|
||||
version = "0.1.0"
|
||||
authors = ["eater <=@eater.me>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
packd = { path = "../packd" }
|
@ -0,0 +1,14 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use packd::unpack;
|
||||
|
||||
#[test]
|
||||
fn proc_macro_works() {
|
||||
let output = unpack!(">hhl", b"\x00\x01\x00\x02\x00\x00\x00\x03");
|
||||
assert_eq!((1, 2, 3), output);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "packd"
|
||||
version = "0.1.0"
|
||||
authors = ["eater <=@eater.me>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
packd-proc-macro = { path = "../packd-proc-macro" }
|
||||
proc-macro-hack = "0.5"
|
||||
byteorder = "1.3.4"
|
@ -0,0 +1,6 @@
|
||||
use proc_macro_hack::proc_macro_hack;
|
||||
|
||||
pub mod parser;
|
||||
|
||||
#[proc_macro_hack]
|
||||
pub use packd_proc_macro::unpack;
|
@ -0,0 +1,38 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian, NativeEndian, ReadBytesExt};
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Endian {
|
||||
NativeEndian,
|
||||
LittleEndian,
|
||||
BigEndian,
|
||||
}
|
||||
|
||||
macro_rules! unpack_num {
|
||||
($type:ty, $endian:expr, $input:expr) => {
|
||||
match $endian {
|
||||
Endian::LittleEndian => <$type>::from_le_bytes($input.try_into().unwrap()),
|
||||
Endian::BigEndian => <$type>::from_be_bytes($input.try_into().unwrap()),
|
||||
Endian::NativeEndian => <$type>::from_ne_bytes($input.try_into().unwrap()),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn unpack_h(endian: Endian, input: &[u8]) -> i16 {
|
||||
unpack_num!(i16, endian, input)
|
||||
}
|
||||
|
||||
pub fn unpack_H(endian: Endian, input: &[u8]) -> u16 {
|
||||
unpack_num!(u16, endian, input)
|
||||
}
|
||||
|
||||
pub fn unpack_l(endian: Endian, input: &[u8]) -> i32 {
|
||||
unpack_num!(i32, endian, input)
|
||||
}
|
||||
|
||||
pub fn unpack_L(endian: Endian, input: &[u8]) -> u32 {
|
||||
unpack_num!(u32, endian, input)
|
||||
}
|
Loading…
Reference in New Issue