parsely_impl/
lib.rs

1mod code_gen;
2pub mod error;
3mod model_types;
4pub mod parsely_read;
5pub mod parsely_write;
6pub(crate) mod syn_helpers;
7
8pub use bits_io::{
9    buf::bit_buf::BitBuf,
10    buf::bit_buf_exts::BitBufExts,
11    buf::bit_buf_mut::BitBufMut,
12    buf::bit_buf_mut_exts::BitBufMutExts,
13    buf::bits::Bits,
14    buf::bits_mut::BitsMut,
15    buf::byte_order::{BigEndian, ByteOrder, LittleEndian, NetworkOrder},
16    io::{bit_cursor::BitCursor, bit_read::BitRead, bit_write::BitWrite},
17};
18
19pub mod nsw_types {
20    pub use bits_io::nsw_types::from_bitslice::BitSliceUxExts;
21    pub use bits_io::nsw_types::*;
22}
23
24pub mod anyhow {
25    pub use anyhow::*;
26}
27
28use code_gen::{
29    read::{
30        parsely_read_enum_data::ParselyReadEnumData,
31        parsely_read_struct_data::ParselyReadStructData,
32    },
33    write::{
34        parsely_write_enum_data::ParselyWriteEnumData,
35        parsely_write_struct_data::ParselyWriteStructData,
36    },
37};
38use darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant};
39use model_types::{Assertion, Context, ExprOrFunc, MapExpr, TypedFnArgList};
40use proc_macro2::TokenStream;
41use quote::quote;
42use syn::DeriveInput;
43use syn_helpers::TypeExts;
44
45#[doc(hidden)]
46pub fn derive_parsely_read(item: TokenStream) -> std::result::Result<TokenStream, syn::Error> {
47    let ast: DeriveInput = syn::parse2(item)?;
48    let data = ParselyReadReceiver::from_derive_input(&ast)?;
49
50    // println!("{data:#?}");
51
52    if data.data.is_struct() {
53        let struct_data = ParselyReadStructData::try_from(data).unwrap();
54        Ok(quote! {
55            #struct_data
56        })
57    } else {
58        let enum_data = ParselyReadEnumData::try_from(data).unwrap();
59        Ok(quote! {
60            #enum_data
61        })
62    }
63}
64
65#[doc(hidden)]
66pub fn derive_parsely_write(item: TokenStream) -> std::result::Result<TokenStream, syn::Error> {
67    let ast: DeriveInput = syn::parse2(item)?;
68    let data = ParselyWriteReceiver::from_derive_input(&ast)?;
69
70    if data.data.is_struct() {
71        let struct_data = ParselyWriteStructData::try_from(data).unwrap();
72        Ok(quote! {
73            #struct_data
74        })
75    } else {
76        let enum_data = ParselyWriteEnumData::try_from(data).unwrap();
77        Ok(quote! {
78            #enum_data
79        })
80    }
81}
82
83#[derive(Debug, FromField, FromMeta)]
84pub struct ParselyCommonFieldReceiver {
85    // Note: 'magic' fields (ident, ty, etc.) don't work with 'flatten' so can't be held here.
86    // See https://github.com/TedDriggs/darling/issues/330
87
88    // generics: Option<syn::Ident>,
89    assertion: Option<Assertion>,
90
91    /// Values that need to be passed as context to this fields read or write method
92    context: Option<Context>,
93
94    /// An optional mapping that will be applied to the read value
95    map: Option<MapExpr>,
96
97    /// An optional indicator that this field is or needs to be aligned to the given byte alignment
98    /// via padding.
99    alignment: Option<usize>,
100}
101
102#[derive(Debug, FromField)]
103#[darling(attributes(parsely, parsely_read))]
104pub struct ParselyReadFieldReceiver {
105    ident: Option<syn::Ident>,
106
107    ty: syn::Type,
108
109    #[darling(flatten)]
110    common: ParselyCommonFieldReceiver,
111    /// 'count' is required when the field is a collection
112    count: Option<syn::Expr>,
113    /// 'while' is an alternate option to 'count' to use with a collection field
114    // #[darling(rename = "while")]
115    // TODO: hopefully can get this to work as 'while'
116    while_pred: Option<syn::Expr>,
117
118    /// Instead of reading the value of this field from the buffer, assign it from the given
119    /// [`syn::Ident`]
120    assign_from: Option<syn::Expr>,
121
122    /// 'when' is required when there's an optional field
123    when: Option<syn::Expr>,
124}
125
126#[derive(Debug, FromVariant)]
127#[darling(attributes(parsely, parsely_read))]
128pub struct ParselyReadVariantReceiver {
129    ident: syn::Ident,
130    discriminant: Option<syn::Expr>,
131    id: syn::Expr,
132    fields: ast::Fields<ParselyReadFieldReceiver>,
133}
134
135#[derive(Debug, FromField)]
136#[darling(attributes(parsely, parsely_write))]
137pub struct ParselyWriteFieldReceiver {
138    ident: Option<syn::Ident>,
139
140    ty: syn::Type,
141
142    #[darling(flatten)]
143    common: ParselyCommonFieldReceiver,
144
145    /// An expression or function call that will be used to update this field in the generated
146    /// `StateSync` implementation for its parent type.
147    sync_expr: Option<ExprOrFunc>,
148
149    /// An list of expressions that should be passed as context to this field's sync method.  The
150    /// sync method provides an opportunity to synchronize "linked" fields, where one field's value
151    /// depends on the value of another.
152    #[darling(default)]
153    sync_with: Context,
154}
155
156#[derive(Debug, FromVariant)]
157#[darling(attributes(parsely, parsely_write))]
158pub struct ParselyWriteVariantReceiver {
159    ident: syn::Ident,
160    discriminant: Option<syn::Expr>,
161    id: syn::Expr,
162    fields: ast::Fields<ParselyWriteFieldReceiver>,
163}
164
165#[derive(Debug, FromDeriveInput)]
166#[darling(attributes(parsely, parsely_read), supports(struct_any, enum_any))]
167pub struct ParselyReadReceiver {
168    ident: syn::Ident,
169    #[darling(default)]
170    required_context: TypedFnArgList,
171    alignment: Option<usize>,
172    // Enums require a type to denote the tag type that determines which variant will be read
173    key_type: Option<syn::Type>,
174    data: ast::Data<ParselyReadVariantReceiver, ParselyReadFieldReceiver>,
175}
176
177#[derive(Debug, FromDeriveInput)]
178#[darling(attributes(parsely, parsely_write), supports(struct_any, enum_any))]
179pub struct ParselyWriteReceiver {
180    ident: syn::Ident,
181    #[darling(default)]
182    required_context: TypedFnArgList,
183    #[darling(default)]
184    sync_args: TypedFnArgList,
185    alignment: Option<usize>,
186    // Enums require a type to denote the tag type to be written to denote the variant
187    key_type: Option<syn::Type>,
188    data: ast::Data<ParselyWriteVariantReceiver, ParselyWriteFieldReceiver>,
189}
190
191pub(crate) fn get_crate_name() -> syn::Ident {
192    let found_crate =
193        proc_macro_crate::crate_name("parsely-rs").expect("parsely-rs is present in Cargo.toml");
194
195    let crate_name = match found_crate {
196        proc_macro_crate::FoundCrate::Itself => "parsely_rs".to_string(),
197        proc_macro_crate::FoundCrate::Name(name) => name,
198    };
199
200    syn::Ident::new(&crate_name, proc_macro2::Span::call_site())
201}