mavspec_rust_derive/lib.rs
1//! MAVSpec Rust Procedural Macros
2//!
3//! <span style="font-size:24px">[πΊπ¦](https://mavka.gitlab.io/home/a_note_on_the_war_in_ukraine/)</span>
4//! [](https://gitlab.com/mavka/libs/mavspec)
5//! [](https://crates.io/crates/mavspec)
6//! [](https://docs.rs/mavspec/latest/mavspec/)
7//! [](https://gitlab.com/mavka/libs/mavspec/-/issues/)
8//!
9//! This crate provides proc macros for [MAVLink](https://mavlink.io/en/) MAVLink entities generated by
10//! [MAVSpec](https://gitlab.com/mavka/libs/mavspec).
11//!
12//! # Usage
13//!
14//! In most cases marcos defined here will be used by autogenerated code. However, it is possible to use them for
15//! creating custom MAVLink entities.
16//!
17//! Custom MAVLink message:
18//!
19//! ```rust
20//! use mavspec::rust::derive::Message;
21//!
22//! #[derive(Clone, Debug, Message)]
23//! #[message_id(255)] // Specify message ID
24//! struct CustomMessage {
25//! scalar_u8: u8,
26//! array_u8_4: [u8; 4],
27//! #[extension] // This marks an extension fields
28//! ext_array_u32_4: [u32; 4],
29//! }
30//! ```
31//!
32//! Custom MAVLink enum:
33//!
34//! ```rust
35//! use mavspec::rust::derive::Enum;
36//!
37//! #[repr(u8)] // Define enum representation
38//! #[derive(Clone, Copy, Debug, Default, Enum)]
39//! enum Variants {
40//! #[default]
41//! OptionA = 0,
42//! OptionB = 1,
43//! OptionC = 2,
44//! }
45//! ```
46//!
47//! Custom MAVLink dialect:
48//!
49//! ```rust
50//! use mavspec::rust::derive::{Dialect, Message};
51//!
52//! #[derive(Clone, Debug, Message)]
53//! #[message_id(255)] // Specify message ID
54//! struct CustomMessage {
55//! scalar_u8: u8,
56//! array_u8_4: [u8; 4],
57//! #[extension] // This marks an extension fields
58//! ext_array_u32_4: [u32; 4],
59//! }
60//!
61//! #[derive(Clone, Debug, Dialect)]
62//! #[dialect(1099)] // Specify unique dialect `ID`
63//! #[version(99)] // Specify dialect version
64//! enum CustomDialect {
65//! Custom(CustomMessage),
66//! }
67//! ```
68
69#![warn(missing_docs)]
70#![deny(rustdoc::broken_intra_doc_links)]
71#![doc(
72 html_logo_url = "https://gitlab.com/mavka/libs/mavspec/-/raw/main/avatar.png?ref_type=heads",
73 html_favicon_url = "https://gitlab.com/mavka/libs/mavspec/-/raw/main/avatar.png?ref_type=heads"
74)]
75
76use syn::DeriveInput;
77
78pub(crate) mod consts;
79pub(crate) mod errors;
80pub(crate) mod field_types;
81mod message_attributes;
82pub(crate) mod message_field;
83
84mod dialect;
85pub(crate) mod enums;
86pub(crate) mod message;
87
88/// Derive MAVLink message from struct.
89///
90/// # Usage
91///
92/// Basic usage:
93///
94/// ```rust
95/// use mavspec::rust::derive::Message;
96///
97/// #[derive(Clone, Debug, Message)]
98/// #[message_id(255)] // Specify message ID
99/// struct CustomMessage {
100/// scalar_u8: u8,
101/// array_u8_4: [u8; 4],
102/// #[extension] // This marks an extension fields
103/// ext_array_u32_4: [u32; 4],
104/// }
105/// ```
106///
107/// It is possible to override `CRC_EXTRA` byte using `#[crc_extra(32)]` attribute:
108///
109/// ```rust
110/// use mavspec::rust::derive::Message;
111///
112/// #[derive(Clone, Debug, Message)]
113/// #[message_id(255)] // Specify message ID
114/// #[crc_extra(32)] // Set `CRC_EXTRA` byte
115/// struct CustomMessage {
116/// scalar_u8: u8,
117/// array_u8_4: [u8; 4],
118/// }
119///
120/// assert_eq!(CustomMessage::crc_extra(), 32);
121/// ```
122///
123/// Auto-calculated `CRC_EXTRA` is not supported for arrays with lengths specified by constants. The
124/// following won't compile:
125///
126/// ```rust,compile_fail
127/// use mavspec::rust::derive::Message;
128///
129/// const FOUR: usize = 4;
130///
131/// #[derive(Clone, Debug, Message)]
132/// #[message_id(255)]
133/// struct CustomMessage {
134/// scalar_u8: u8,
135/// array_u8_4: [u8; FOUR], // Can't calculate `CRC_EXTRA`
136/// }
137/// ```
138///
139/// ## Enums
140///
141/// It is possible to use custom enums as message fields types with `#[derive(Enum)]`. Each enum should have a numeric
142/// representation and implement [`Default`] trait.
143///
144/// Fields with custom types should be attributed with `base_type` attribute (i.e. `#[base_type(u16)]`) that specifies
145/// actual base type of a field. For arrays base type is a type of their elements.
146///
147/// It is possible to use larger base types with smaller enum representation types. In such case you have to specify the
148/// representation type of an enum with `repr_type` attribute (i.e. `#[repr_type(u8)]`).
149///
150/// It is forbidden to use larger representation types with smaller base types.
151///
152/// ```rust
153/// use mavspec::rust::derive::{Enum, Message};
154///
155/// #[repr(u8)] // Define enum representation
156/// #[derive(Clone, Copy, Debug, Default, Enum)]
157/// enum Variants {
158/// #[default]
159/// OptionA = 0,
160/// OptionB = 1,
161/// OptionC = 2,
162/// }
163///
164/// #[derive(Clone, Debug, Message)]
165/// #[message_id(255)]
166/// struct CustomMessage {
167/// #[base_type(u8)]
168/// scalar_u8: Variants,
169///
170/// #[base_type(u8)]
171/// array_u8_4: [Variants; 4],
172///
173/// #[base_type(u16)]
174/// #[repr_type(u8)]
175/// large_scalar_u16: Variants,
176///
177/// #[base_type(u16)]
178/// #[repr_type(u8)]
179/// large_array_u16_4: [Variants; 4],
180/// }
181/// ```
182///
183/// ## Bitmasks
184///
185/// For bitmasks you can use native [bitflags](https://crates.io/crates/bitflags) flags. In such case you have to
186/// attribute corresponding fields with `#[bitmask]` attribute. In the same spirit as for [enums](#enums) you have to
187/// set `base_type` type and `repr_type` attributes.
188///
189/// ```rust
190/// use mavspec::rust::derive::Message;
191/// use bitflags::bitflags;
192///
193/// bitflags! {
194/// #[derive(Clone, Copy, Debug, Default)]
195/// struct Flags: u8 {
196/// const FLAG_8 = 1;
197/// const FLAG_7 = 1 << 1;
198/// const FLAG_6 = 1 << 2;
199/// const FLAG_5 = 1 << 3;
200/// const FLAG_4 = 1 << 4;
201/// const FLAG_3 = 1 << 5;
202/// const FLAG_2 = 1 << 6;
203/// const FLAG_1 = 1 << 7;
204/// }
205/// }
206///
207/// #[derive(Clone, Debug, Message)]
208/// #[message_id(255)]
209/// struct CustomMessage {
210/// #[bitmask]
211/// #[base_type(u8)]
212/// scalar_u8: Flags,
213///
214/// #[bitmask]
215/// #[base_type(u8)]
216/// array_u8_4: [Flags; 4],
217///
218/// #[bitmask]
219/// #[base_type(u16)]
220/// #[repr_type(u8)]
221/// large_scalar_u16: Flags,
222///
223/// #[bitmask]
224/// #[base_type(u16)]
225/// #[repr_type(u8)]
226/// large_array_u16_4: [Flags; 4],
227/// }
228/// ```
229#[proc_macro_derive(
230 Message,
231 attributes(message_id, crc_extra, extension, base_type, repr_type, bitmask)
232)]
233pub fn derive_mavlink_message(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
234 let input: DeriveInput = syn::parse(input).unwrap();
235
236 let message = match message::Message::try_from(input) {
237 Ok(message) => message,
238 Err(err) => panic!("{}", err),
239 };
240
241 proc_macro::TokenStream::from(message.to_token_stream())
242}
243
244/// Derive MAVLink enum from enum.
245///
246/// # Usage
247///
248/// Basic usage:
249///
250/// ```rust
251/// use mavspec::rust::derive::Enum;
252///
253/// #[repr(u8)]
254/// #[derive(Clone, Copy, Debug, Default, Enum)]
255/// enum CustomEnum {
256/// #[default]
257/// OptionA = 0,
258/// OptionB = 1,
259/// OptionC = 2,
260/// }
261/// ```
262///
263/// It is possible to use constants in variant discriminants:
264///
265/// ```rust
266/// use mavspec::rust::derive::Enum;
267///
268/// const ONE: u8 = 1;
269/// const TWO: u8 = 2;
270///
271/// #[derive(Enum)]
272/// #[repr(u8)]
273/// #[derive(Copy, Clone, Debug, Default)]
274/// enum CustomEnum {
275/// #[default]
276/// OptionA = 0,
277/// OptionB = ONE, // Constants are supported
278/// OptionC = TWO, //
279/// }
280/// ```
281///
282#[proc_macro_derive(Enum)]
283pub fn derive_mavlink_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
284 let input: DeriveInput = syn::parse(input).unwrap();
285
286 let mav_enum = match enums::Enum::try_from(input) {
287 Ok(mav_enum) => mav_enum,
288 Err(err) => panic!("{}", err),
289 };
290
291 proc_macro::TokenStream::from(mav_enum.to_token_stream())
292}
293
294/// Derive MAVLink dialect from enum.
295///
296/// # Usage
297///
298/// Create a simple ad-hoc dialect for meaningless smalltalk:
299///
300/// ```rust
301/// use mavspec::rust::derive::{Dialect, Enum, Message};
302///
303/// #[repr(u8)]
304/// #[derive(Copy, Clone, Debug, Default, Enum)]
305/// enum Mood {
306/// #[default]
307/// Serious = 0,
308/// Grumpy = 1,
309/// Delighted = 2,
310/// Confused = 3,
311/// }
312///
313/// #[derive(Clone, Debug, Message)]
314/// #[message_id(42)]
315/// struct Howdy {
316/// #[base_type(u8)]
317/// mood: Mood,
318/// }
319///
320/// #[derive(Clone, Debug, Message)]
321/// #[message_id(43)]
322/// struct FineAndYou {
323/// #[base_type(u8)]
324/// mood: Mood,
325/// }
326///
327/// #[derive(Clone, Debug, Dialect)]
328/// #[dialect(1099)]
329/// #[version(99)]
330/// enum SmallTalk {
331/// Howdy(Howdy),
332/// FineAndYou(FineAndYou),
333/// }
334/// ```
335#[proc_macro_derive(Dialect, attributes(name, dialect, version))]
336pub fn derive_mavlink_dialect(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
337 let input: DeriveInput = syn::parse(input).unwrap();
338
339 let dialect = match dialect::Dialect::try_from(input) {
340 Ok(dialect) => dialect,
341 Err(err) => panic!("{}", err),
342 };
343
344 proc_macro::TokenStream::from(dialect.to_token_stream())
345}