libatk_derive/
lib.rs

1use proc_macro::TokenStream;
2
3use quote::quote;
4use syn::parse::{Parse, ParseStream};
5use syn::punctuated::Punctuated;
6use syn::{parse_macro_input, DeriveInput, Result, Token};
7use syn::{Expr, ExprLit, Lit, MetaNameValue};
8
9/// Struct for holding the parsed attribute arguments.
10struct Attributes {
11    base_offset: usize,
12    report_id: u8,
13    cmd_len: usize,
14}
15
16impl Parse for Attributes {
17    fn parse(input: ParseStream) -> Result<Self> {
18        // Parse the comma-separated list of key = value pairs.
19        let args = Punctuated::<MetaNameValue, Token![,]>::parse_terminated(input)?;
20        let mut base_offset_opt = None;
21        let mut report_id_opt = None;
22        let mut cmd_len_opt = None;
23
24        for arg in args {
25            // Get the key as a string.
26            let key = arg
27                .path
28                .get_ident()
29                .ok_or_else(|| syn::Error::new_spanned(&arg.path, "Expected identifier"))?
30                .to_string();
31
32            // For each arg, we now extract the literal from the expression.
33            let lit_int = if let Expr::Lit(ExprLit {
34                lit: Lit::Int(ref i),
35                ..
36            }) = arg.value
37            {
38                i
39            } else {
40                return Err(syn::Error::new_spanned(
41                    &arg.value,
42                    "Expected integer literal",
43                ));
44            };
45
46            match key.as_str() {
47                "base_offset" => {
48                    base_offset_opt = Some(lit_int.base10_parse()?);
49                }
50                "report_id" => {
51                    report_id_opt = Some(lit_int.base10_parse()?);
52                }
53                "cmd_len" => {
54                    cmd_len_opt = Some(lit_int.base10_parse()?);
55                }
56                _ => return Err(syn::Error::new_spanned(arg, "Unknown attribute key")),
57            }
58        }
59
60        // Ensure all required fields were provided.
61        let base_offset = base_offset_opt
62            .ok_or_else(|| syn::Error::new(input.span(), "Missing `base_offset`"))?;
63        let report_id =
64            report_id_opt.ok_or_else(|| syn::Error::new(input.span(), "Missing `report_id`"))?;
65        let cmd_len =
66            cmd_len_opt.ok_or_else(|| syn::Error::new(input.span(), "Missing `cmd_len`"))?;
67
68        Ok(Attributes {
69            base_offset,
70            report_id,
71            cmd_len,
72        })
73    }
74}
75
76#[proc_macro_derive(CommandDescriptor, attributes(command_descriptor))]
77pub fn derive_my_trait(input: TokenStream) -> TokenStream {
78    let ast = parse_macro_input!(input as DeriveInput);
79
80    let mut args_opt = None;
81    for attr in ast.attrs.iter() {
82        if attr.path().is_ident("command_descriptor") {
83            // Instead of parse_meta, we use parse_args to parse the tokens within parentheses.
84            let args: Attributes = attr
85                .parse_args()
86                .expect("Failed to parse command_descriptor arguments");
87            args_opt = Some(args);
88            break;
89        }
90    }
91
92    let args = args_opt.expect("Missing #[command_descriptor(...)] attribute");
93    let base_offset = args.base_offset;
94    let report_id = args.report_id;
95    let cmd_len = args.cmd_len;
96
97    let name = &ast.ident;
98
99    let gen = quote! {
100        impl CommandDescriptor for #name {
101            fn base_offset() -> usize {
102                #base_offset
103            }
104
105            fn report_id() -> u8 {
106                #report_id
107            }
108
109            fn cmd_len() -> usize {
110                #cmd_len
111            }
112        }
113    };
114
115    TokenStream::from(gen)
116}