ethercrab_wire_derive/
lib.rs

1//! Derive attributes for [`ethercrab-wire`].
2//!
3//! # Experimental
4//!
5//! This crate is in its early stages and may contain bugs or publish breaking changes at any time.
6//! It is in use by [`ethercrab`] and is well exercised there, but please use with caution in your
7//! own code.
8//!
9//! These derives support both structs with bit- and (multi)byte-sized fields for structs, as well
10//! as enums with optional catch-all variant.
11//!
12//! # Supported attributes
13//!
14//! ## Structs
15//!
16//! - `#[wire(bits = N)]` OR `#[wire(bytes = N)]`
17//!
18//!   The size of this struct when packed on the wire. These attributes may not be present at the
19//!   same time.
20//!
21//! ## Struct fields
22//!
23//! - `#[wire(bits = N)]` OR `#[wire(bytes = N)]`
24//!
25//!   How many bytes this field consumes on the wire. These attributes may not be present at the
26//!   same time.
27//!
28//! - `#[wire(pre_skip = N)]` OR `#[wire(pre_skip_bytes = N)]`
29//!
30//!   Skip one or more whole bytes before or after this field in the packed representation.
31//!
32//! - `#[wire(post_skip = N)]` OR `#[wire(post_skip_bytes = N)]`
33//!
34//!   How many bits or bytes to skip in the raw data **after** this field.
35//!
36//!   These attributes are only applicable to fields that are less than 8 bits wide.
37//!
38//! ## Enums
39//!
40//! Enums must have a `#[repr()]` attribute, as well as implement the `Copy` trait.
41//!
42//! ## Enum discriminants
43//!
44//! Enum discriminants may not contain fields.
45//!
46//! - `#[wire(alternatives = [])]`
47//!
48//!   A discriminant with this attribute will be parsed successfully if either its direct value or
49//!   any of the listed alternatives are found in the input data.
50//!
51//!   The discriminant value is used when packing _to_ the wire.
52//!
53//! - `#[wire(catch_all)]`
54//!
55//!   Apply this once to a discriminant with a single unnamed field the same type as the enum's
56//!   `#[repr()]` to catch any unrecognised values.
57//!
58//! # Examples
59//!
60//! ## A struct with both bit fields and multi-byte fields.
61//!
62//! ```rust
63//! #[derive(ethercrab_wire::EtherCrabWireReadWrite)]
64//! #[wire(bytes = 4)]
65//! struct Mixed {
66//!     #[wire(bits = 1)]
67//!     one_bit: u8,
68//!     #[wire(bits = 2)]
69//!     two_bits: u8,
70//!
71//!     // Fields that are 8 bits or larger must be byte aligned, so we skip the two remaining bits
72//!     // of the previous byte with `post_skip`.
73//!     #[wire(bits = 3, post_skip = 2)]
74//!     three_bits: u8,
75//!
76//!     /// Whole `u8`
77//!     #[wire(bytes = 1)]
78//!     one_byte: u8,
79//!
80//!     /// Whole `u16`
81//!     #[wire(bytes = 2)]
82//!     one_word: u16,
83//! }
84//! ```
85//!
86//! ## Enum with catch all discriminant and alternatives
87//!
88//! ```rust
89//! # use ethercrab_wire::EtherCrabWireRead;
90//! # #[derive(PartialEq, Debug)]
91//! #[derive(Copy, Clone, ethercrab_wire::EtherCrabWireReadWrite)]
92//! #[repr(u8)]
93//! enum OneByte {
94//!     Foo = 0x01,
95//!     #[wire(alternatives = [ 3, 4, 5, 6 ])]
96//!     Bar = 0x02,
97//!     Baz = 0x07,
98//!     Quux = 0xab,
99//!     #[wire(catch_all)]
100//!     Unknown(u8),
101//! }
102//!
103//! // Normal discriminant
104//! assert_eq!(OneByte::unpack_from_slice(&[0x07]), Ok(OneByte::Baz));
105//!
106//! // Alternative value for `Bar`
107//! assert_eq!(OneByte::unpack_from_slice(&[0x05]), Ok(OneByte::Bar));
108//!
109//! // Catch all
110//! assert_eq!(OneByte::unpack_from_slice(&[0xaa]), Ok(OneByte::Unknown(0xaa)));
111//! ```
112//!
113//! # Struct field alignment
114//!
115//! Struct fields of 1 byte or more MUST be byte-aligned. For example, the following struct will be
116//! rejected due to `bar` being 5 bits "early":
117//!
118//! ```rust,compile_fail
119//! #[derive(ethercrab_wire::EtherCrabWireReadWrite)]
120//! #[wire(bytes = 2)]
121//! struct Broken {
122//!     #[wire(bits = 3)]
123//!     foo: u8,
124//!
125//!     // There are 5 bits here unaccounted for
126//!
127//!     #[wire(bytes = 1)]
128//!     bar: u8,
129//! }
130//! ```
131//!
132//! This can easily be fixed by using the `pre_skip` or `post_skip` attributes to realign the next
133//! field to 8 bits (or skip whole bytes of the input data):
134//!
135//! ```rust
136//! #[derive(ethercrab_wire::EtherCrabWireReadWrite)]
137//! #[wire(bytes = 2)]
138//! struct Fixed {
139//!     #[wire(bits = 3, post_skip = 5)]
140//!     foo: u8,
141//!     #[wire(bytes = 1)]
142//!     bar: u8,
143//! }
144//! ```
145//!
146//! A field in the middle of a byte can be written as such, maintaining 8 bit alignment:
147//!
148//! ```rust
149//! #[derive(ethercrab_wire::EtherCrabWireReadWrite)]
150//! #[wire(bytes = 1)]
151//! struct Middle {
152//!     #[wire(pre_skip = 2, bits = 3, post_skip = 3)]
153//!     foo: u8,
154//! }
155//! ```
156//!
157//! [`ethercrab`]: https://docs.rs/ethercrab
158//! [`ethercrab-wire`]: https://docs.rs/ethercrab-wire
159
160#![deny(missing_docs)]
161#![deny(missing_copy_implementations)]
162#![deny(trivial_casts)]
163#![deny(trivial_numeric_casts)]
164#![deny(unused_import_braces)]
165#![deny(unused_qualifications)]
166#![deny(rustdoc::broken_intra_doc_links)]
167#![deny(rustdoc::private_intra_doc_links)]
168
169mod generate_enum;
170mod generate_struct;
171mod help;
172mod parse_enum;
173mod parse_struct;
174
175use generate_enum::{generate_enum_read, generate_enum_write};
176use generate_struct::{generate_sized_impl, generate_struct_read, generate_struct_write};
177use parse_enum::parse_enum;
178use parse_struct::parse_struct;
179use proc_macro::TokenStream;
180use syn::{parse_macro_input, Data, DeriveInput};
181
182/// Items that can be written to and read from the wire.
183///
184/// Please see the [crate documentation](index.html) for examples and supported
185/// attributes.
186///
187/// For write-only items, see [`EtherCrabWireWrite`]. For read-only items, see
188/// [`EtherCrabWireRead`].
189#[proc_macro_derive(EtherCrabWireReadWrite, attributes(wire))]
190pub fn ether_crab_wire(input: TokenStream) -> TokenStream {
191    let input = parse_macro_input!(input as DeriveInput);
192
193    let res = match input.clone().data {
194        Data::Enum(e) => parse_enum(e, input.clone()).map(|parsed| {
195            let mut tokens = generate_enum_write(parsed.clone(), &input, false);
196
197            tokens.extend(generate_enum_read(parsed, &input));
198
199            tokens
200        }),
201        Data::Struct(s) => parse_struct(s, input.clone()).map(|parsed| {
202            let mut tokens = generate_struct_write(&parsed, &input);
203
204            tokens.extend(generate_struct_read(&parsed, &input));
205
206            tokens.extend(generate_sized_impl(&parsed, &input));
207
208            tokens
209        }),
210        Data::Union(_) => Err(syn::Error::new(
211            input.ident.span(),
212            "Unions are not supported",
213        )),
214    };
215
216    let res = match res {
217        Ok(res) => res,
218        Err(e) => return e.to_compile_error().into(),
219    };
220
221    TokenStream::from(res)
222}
223
224/// Items that can only be read from the wire.
225///
226/// Please see the [crate documentation](index.html) for examples and supported attributes.
227///
228/// For read/write items, see [`EtherCrabWireReadWrite`]. For write-only items, see
229/// [`EtherCrabWireWrite`].
230#[proc_macro_derive(EtherCrabWireRead, attributes(wire))]
231pub fn ether_crab_wire_read(input: TokenStream) -> TokenStream {
232    let input = parse_macro_input!(input as DeriveInput);
233
234    let res = match input.clone().data {
235        Data::Enum(e) => {
236            parse_enum(e, input.clone()).map(|parsed| generate_enum_read(parsed, &input))
237        }
238        Data::Struct(s) => parse_struct(s, input.clone()).map(|parsed| {
239            let mut tokens = generate_struct_read(&parsed, &input);
240
241            tokens.extend(generate_sized_impl(&parsed, &input));
242
243            tokens
244        }),
245        Data::Union(_) => Err(syn::Error::new(
246            input.ident.span(),
247            "Unions are not supported",
248        )),
249    };
250
251    let res = match res {
252        Ok(res) => res,
253        Err(e) => return e.to_compile_error().into(),
254    };
255
256    TokenStream::from(res)
257}
258
259/// Items that can only be written to the wire.
260///
261/// Please see the [crate documentation](index.html) for examples and supported attributes.
262///
263/// For read/write items, see [`EtherCrabWireReadWrite`]. For read-only items, see
264/// [`EtherCrabWireRead`].
265#[proc_macro_derive(EtherCrabWireWrite, attributes(wire))]
266pub fn ether_crab_wire_write(input: TokenStream) -> TokenStream {
267    let input = parse_macro_input!(input as DeriveInput);
268
269    let res = match input.clone().data {
270        Data::Enum(e) => {
271            parse_enum(e, input.clone()).map(|parsed| generate_enum_write(parsed, &input, true))
272        }
273        Data::Struct(s) => parse_struct(s, input.clone()).map(|parsed| {
274            let mut tokens = generate_struct_write(&parsed, &input);
275
276            tokens.extend(generate_sized_impl(&parsed, &input));
277
278            tokens
279        }),
280        Data::Union(_) => Err(syn::Error::new(
281            input.ident.span(),
282            "Unions are not supported",
283        )),
284    };
285
286    let res = match res {
287        Ok(res) => res,
288        Err(e) => return e.to_compile_error().into(),
289    };
290
291    TokenStream::from(res)
292}
293
294#[cfg(test)]
295mod tests {
296    #[test]
297    fn trybuild_cases() {
298        let t = trybuild::TestCases::new();
299
300        t.compile_fail("ui/*.rs");
301    }
302}