device_driver_macros/
lib.rs

1#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
2
3use std::{fs::File, io::Read, ops::Deref, path::PathBuf};
4
5use proc_macro::TokenStream;
6use proc_macro2::Span;
7use syn::{Ident, LitStr, braced};
8
9/// Macro to implement the device driver.
10///
11/// ## Usage:
12///
13/// DSL:
14/// ```rust,ignore
15/// # use device_driver_macros::create_device;
16/// create_device!(
17///     device_name: MyTestDevice,
18///     dsl: {
19///         // DSL
20///     }
21/// );
22/// ```
23///
24/// Manifest:
25/// ```rust,ignore
26/// # use device_driver_macros::create_device;
27/// create_device!(
28///     device_name: MyTestDevice,
29///     manifest: "path/to/manifest/file.json"
30/// );
31/// ```
32#[proc_macro]
33pub fn create_device(item: TokenStream) -> TokenStream {
34    let input = match syn::parse::<Input>(item) {
35        Ok(i) => i,
36        Err(e) => return e.into_compile_error().into(),
37    };
38
39    match input.generation_type {
40        #[cfg(feature = "dsl")]
41        GenerationType::Dsl(tokens) => {
42            device_driver_generation::transform_dsl(tokens, &input.device_name.to_string())
43                .parse()
44                .unwrap()
45        }
46        #[cfg(not(feature = "dsl"))]
47        GenerationType::Dsl(_tokens) => {
48            syn::Error::new(Span::call_site(), format!("The dsl feature is not enabled"))
49                .into_compile_error()
50                .into()
51        }
52        GenerationType::Manifest(path) => {
53            let result: Result<String, syn::Error> =
54                (|| {
55                    let mut path = PathBuf::from(path.value());
56                    if path.is_relative() {
57                        let manifest_dir =
58                            PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
59                        path = manifest_dir.join(path);
60                    }
61
62                    let mut file_contents = String::new();
63                    File::open(&path)
64                        .map_err(|e| {
65                            syn::Error::new(
66                                Span::call_site(),
67                                format!(
68                                    "Could not open the manifest file at '{}': {e}",
69                                    path.display()
70                                ),
71                            )
72                        })?
73                        .read_to_string(&mut file_contents)
74                        .unwrap();
75
76                    let extension = path.extension().map(|ext| ext.to_string_lossy()).ok_or(
77                        syn::Error::new(Span::call_site(), "Manifest file has no file extension"),
78                    )?;
79
80                    match extension.deref() {
81                        #[cfg(feature = "json")]
82                        "json" => Ok(device_driver_generation::transform_json(
83                            &file_contents,
84                            &input.device_name.to_string(),
85                        )),
86                        #[cfg(not(feature = "json"))]
87                        "json" => Err(syn::Error::new(
88                            Span::call_site(),
89                            format!("The json feature is not enabled"),
90                        )),
91                        #[cfg(feature = "yaml")]
92                        "yaml" => Ok(device_driver_generation::transform_yaml(
93                            &file_contents,
94                            &input.device_name.to_string(),
95                        )),
96                        #[cfg(not(feature = "yaml"))]
97                        "yaml" => Err(syn::Error::new(
98                            Span::call_site(),
99                            format!("The yaml feature is not enabled"),
100                        )),
101                        #[cfg(feature = "toml")]
102                        "toml" => Ok(device_driver_generation::transform_toml(
103                            &file_contents,
104                            &input.device_name.to_string(),
105                        )),
106                        #[cfg(not(feature = "toml"))]
107                        "toml" => Err(syn::Error::new(
108                            Span::call_site(),
109                            format!("The toml feature is not enabled"),
110                        )),
111                        #[cfg(feature = "dsl")]
112                        "dsl" => Ok(device_driver_generation::transform_dsl(
113                            syn::parse_str(&file_contents)?,
114                            &input.device_name.to_string(),
115                        )),
116                        #[cfg(not(feature = "dsl"))]
117                        "dsl" => Err(syn::Error::new(
118                            Span::call_site(),
119                            format!("The dsl feature is not enabled"),
120                        )),
121                        unknown => Err(syn::Error::new(
122                            Span::call_site(),
123                            format!("Unknown manifest file extension: '{unknown}'"),
124                        )),
125                    }
126                })();
127
128            match result {
129                Ok(tokens) => tokens.parse().unwrap(),
130                Err(e) => e.into_compile_error().into(),
131            }
132        }
133    }
134}
135
136struct Input {
137    device_name: Ident,
138    generation_type: GenerationType,
139}
140
141enum GenerationType {
142    Dsl(proc_macro2::TokenStream),
143    Manifest(LitStr),
144}
145
146impl syn::parse::Parse for Input {
147    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
148        input.parse::<kw::device_name>()?;
149        input.parse::<syn::Token![:]>()?;
150        let device_name = input.parse()?;
151        input.parse::<syn::Token![,]>()?;
152
153        let look = input.lookahead1();
154
155        if look.peek(kw::dsl) {
156            input.parse::<kw::dsl>()?;
157            input.parse::<syn::Token![:]>()?;
158
159            let braced;
160            braced!(braced in input);
161
162            let tokens = braced.parse()?;
163
164            Ok(Self {
165                device_name,
166                generation_type: GenerationType::Dsl(tokens),
167            })
168        } else if look.peek(kw::manifest) {
169            input.parse::<kw::manifest>()?;
170            input.parse::<syn::Token![:]>()?;
171
172            let path = input.parse()?;
173
174            Ok(Self {
175                device_name,
176                generation_type: GenerationType::Manifest(path),
177            })
178        } else {
179            Err(look.error())
180        }
181    }
182}
183
184mod kw {
185    syn::custom_keyword!(device_name);
186    syn::custom_keyword!(dsl);
187    syn::custom_keyword!(manifest);
188}