mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 10:14:06 +02:00
parse derive macro
This commit is contained in:
parent
5f4af8333b
commit
f5e236c58b
4 changed files with 249 additions and 0 deletions
45
parse_gen/Cargo.lock
generated
Normal file
45
parse_gen/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "0.4.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "0.6.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "0.15.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tf-demo-demo-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
|
||||||
|
"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
|
||||||
|
"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
|
||||||
|
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||||
18
parse_gen/Cargo.toml
Normal file
18
parse_gen/Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "tf-demo-parser-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "tf_demo_parse_derive"
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "0.15"
|
||||||
|
quote = "0.6"
|
||||||
|
proc-macro2 = "0.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tf-demo-parser = { path = ".." }
|
||||||
|
bitstream_reader = { path = "../../../bitbuffer" }
|
||||||
149
parse_gen/src/lib.rs
Normal file
149
parse_gen/src/lib.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro2::{TokenStream, Span};
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use quote::{TokenStreamExt, ToTokens};
|
||||||
|
use syn::{Data, DeriveInput, Field, Fields, GenericParam, Generics, Ident, Index, Lit, Meta, parse_macro_input, parse_quote, Path, Token, Type, TypePath};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
#[proc_macro_derive(Parse, attributes(size))]
|
||||||
|
pub fn derive_helper_attr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
// Used in the quasi-quotation below as `#name`.
|
||||||
|
let name = input.ident;
|
||||||
|
|
||||||
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
|
||||||
|
let parse = parse(&input.data, &name);
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl #impl_generics ::tf_demo_parser::Parse<'_> for #name #ty_generics #where_clause {
|
||||||
|
fn parse(stream: &mut ::tf_demo_parser::Stream, _state: &::tf_demo_parser::ParserState) -> ::tf_demo_parser::Result<Self> {
|
||||||
|
Ok(#parse)
|
||||||
|
}
|
||||||
|
fn skip(stream: &mut ::tf_demo_parser::Stream) -> ::tf_demo_parser::Result<()> {
|
||||||
|
// TODO
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//panic!("{}", TokenStream::to_string(&expanded));
|
||||||
|
|
||||||
|
// Hand the output tokens back to the compiler
|
||||||
|
proc_macro::TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a bound `T: HeapSize` to every type parameter T.
|
||||||
|
fn add_trait_bounds(mut generics: Generics) -> Generics {
|
||||||
|
for param in &mut generics.params {
|
||||||
|
if let GenericParam::Type(ref mut type_param) = *param {
|
||||||
|
type_param.bounds.push(parse_quote!(::heapsize::HeapSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generics
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(data: &Data, struct_name: &Ident) -> TokenStream {
|
||||||
|
match *data {
|
||||||
|
Data::Struct(ref data) => {
|
||||||
|
match data.fields {
|
||||||
|
Fields::Named(ref fields) => {
|
||||||
|
let recurse = fields.named.iter().map(|f| {
|
||||||
|
let name = &f.ident;
|
||||||
|
// Get attributes `#[..]` on each field
|
||||||
|
let size = get_field_size(f);
|
||||||
|
let parser = parse_type(&f.ty, size);
|
||||||
|
quote_spanned! { f.span()=>
|
||||||
|
#name: #parser?,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
#struct_name {
|
||||||
|
#(#recurse)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_field_size(field: &Field) -> Option<usize> {
|
||||||
|
for attr in field.attrs.iter() {
|
||||||
|
// Parse the attribute
|
||||||
|
let meta = attr.parse_meta().unwrap();
|
||||||
|
match meta {
|
||||||
|
// Matches attribute with single word like `#[nng_member]` (as opposed to `#[derive(NngGetOps)]` or `#[nng_member = "socket"]`)
|
||||||
|
Meta::NameValue(ref nameValue) if nameValue.ident == "size" =>
|
||||||
|
match &nameValue.lit {
|
||||||
|
Lit::Int(size) => {
|
||||||
|
return Some(size.value() as usize);
|
||||||
|
}
|
||||||
|
_ => panic!("Invalid size for field")
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_type(ty: &Type, size: Option<usize>) -> TokenStream {
|
||||||
|
match ty {
|
||||||
|
Type::Path(path) => {
|
||||||
|
let types: Vec<(TypePath, Option<usize>, &str)> = vec![
|
||||||
|
(parse_quote!(String), None, "read_string"),
|
||||||
|
(parse_quote!(bool), None, "read"),
|
||||||
|
(parse_quote!(u8), Some(8), "read"),
|
||||||
|
(parse_quote!(u16), Some(16), "read"),
|
||||||
|
(parse_quote!(u32), Some(32), "read"),
|
||||||
|
(parse_quote!(i8), Some(8), "read"),
|
||||||
|
(parse_quote!(i16), Some(16), "read"),
|
||||||
|
(parse_quote!(i32), Some(32), "read"),
|
||||||
|
(parse_quote!(f32), None, "read_float"),
|
||||||
|
(parse_quote!(f64), None, "read_float"),
|
||||||
|
];
|
||||||
|
for (type_option, default_size, method) in types {
|
||||||
|
let method_ident = Ident::new(method,Span::call_site());
|
||||||
|
if type_option == *path {
|
||||||
|
match default_size {
|
||||||
|
Some(default) => {
|
||||||
|
let size = size.unwrap_or(default);
|
||||||
|
return quote! {
|
||||||
|
stream.#method_ident(#size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if method == "read_string" {
|
||||||
|
let size = QuoteOption(size);
|
||||||
|
return quote! {
|
||||||
|
stream.#method_ident(#size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return quote! {
|
||||||
|
stream.#method_ident()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unimplemented!("a");
|
||||||
|
}
|
||||||
|
_ => unimplemented!("b"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QuoteOption<T>(Option<T>);
|
||||||
|
|
||||||
|
impl<T: ToTokens> ToTokens for QuoteOption<T> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
tokens.append_all(match self.0 {
|
||||||
|
Some(ref t) => quote! { ::std::option::Option::Some(#t) },
|
||||||
|
None => quote! { ::std::option::Option::None },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
37
parse_gen/tests/test.rs
Normal file
37
parse_gen/tests/test.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use bitstream_reader::{BitBuffer, BitStream, LittleEndian};
|
||||||
|
|
||||||
|
use tf_demo_parse_derive::Parse;
|
||||||
|
use tf_demo_parser::Parse;
|
||||||
|
use tf_demo_parser::ParserState;
|
||||||
|
|
||||||
|
#[derive(Parse, PartialEq, Debug)]
|
||||||
|
struct TestStruct {
|
||||||
|
foo: u8,
|
||||||
|
str: String,
|
||||||
|
#[size = 2]
|
||||||
|
truncated: String,
|
||||||
|
bar: u16,
|
||||||
|
float: f32,
|
||||||
|
#[size = 3]
|
||||||
|
asd: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_struct() {
|
||||||
|
let float: [u8; 4] = 12.5f32.to_bits().to_le_bytes();
|
||||||
|
let bytes = &[
|
||||||
|
12, 'h' as u8, 'e' as u8, 'l' as u8, 'l' as u8, 'o' as u8, 0, 'f' as u8,
|
||||||
|
'o' as u8, 'o' as u8, 0, float[0], float[1], float[2], float[3], 0b0101_0101
|
||||||
|
];
|
||||||
|
let buffer = BitBuffer::new(bytes, LittleEndian);
|
||||||
|
let mut stream = BitStream::new(buffer, None, None);
|
||||||
|
let state = ParserState::new(&stream);
|
||||||
|
assert_eq!(TestStruct {
|
||||||
|
foo: 12,
|
||||||
|
str: "hello".to_owned(),
|
||||||
|
truncated: "fo".to_owned(),
|
||||||
|
bar: 'o' as u16,
|
||||||
|
float: 12.5,
|
||||||
|
asd: 0b101,
|
||||||
|
}, TestStruct::parse(&mut stream, &state).unwrap());
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue