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()).into()
43        }
44        #[cfg(not(feature = "dsl"))]
45        GenerationType::Dsl(_tokens) => {
46            syn::Error::new(Span::call_site(), format!("The dsl feature is not enabled"))
47                .into_compile_error()
48                .into()
49        }
50        GenerationType::Manifest(path) => {
51            let result: Result<proc_macro2::TokenStream, syn::Error> = (|| {
52                let mut path = PathBuf::from(path.value());
53                if path.is_relative() {
54                    let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
55                    path = manifest_dir.join(path);
56                }
57
58                let mut file_contents = String::new();
59                File::open(&path)
60                    .map_err(|e| {
61                        syn::Error::new(
62                            Span::call_site(),
63                            format!("Could open the manifest file at '{}': {e}", path.display()),
64                        )
65                    })?
66                    .read_to_string(&mut file_contents)
67                    .unwrap();
68
69                let extension =
70                    path.extension()
71                        .map(|ext| ext.to_string_lossy())
72                        .ok_or(syn::Error::new(
73                            Span::call_site(),
74                            "Manifest file has no file extension",
75                        ))?;
76
77                match extension.deref() {
78                    #[cfg(feature = "json")]
79                    "json" => Ok(device_driver_generation::transform_json(
80                        &file_contents,
81                        &input.device_name.to_string(),
82                    )),
83                    #[cfg(not(feature = "json"))]
84                    "json" => Err(syn::Error::new(
85                        Span::call_site(),
86                        format!("The json feature is not enabled"),
87                    )),
88                    #[cfg(feature = "yaml")]
89                    "yaml" => Ok(device_driver_generation::transform_yaml(
90                        &file_contents,
91                        &input.device_name.to_string(),
92                    )),
93                    #[cfg(not(feature = "yaml"))]
94                    "yaml" => Err(syn::Error::new(
95                        Span::call_site(),
96                        format!("The yaml feature is not enabled"),
97                    )),
98                    #[cfg(feature = "toml")]
99                    "toml" => Ok(device_driver_generation::transform_toml(
100                        &file_contents,
101                        &input.device_name.to_string(),
102                    )),
103                    #[cfg(not(feature = "toml"))]
104                    "toml" => Err(syn::Error::new(
105                        Span::call_site(),
106                        format!("The toml feature is not enabled"),
107                    )),
108                    #[cfg(feature = "dsl")]
109                    "dsl" => Ok(device_driver_generation::transform_dsl(
110                        syn::parse_str(&file_contents)?,
111                        &input.device_name.to_string(),
112                    )),
113                    #[cfg(not(feature = "dsl"))]
114                    "dsl" => Err(syn::Error::new(
115                        Span::call_site(),
116                        format!("The dsl feature is not enabled"),
117                    )),
118                    unknown => Err(syn::Error::new(
119                        Span::call_site(),
120                        format!("Unknown manifest file extension: '{unknown}'"),
121                    )),
122                }
123            })();
124
125            match result {
126                Ok(tokens) => tokens.into(),
127                Err(e) => e.into_compile_error().into(),
128            }
129        }
130    }
131}
132
133struct Input {
134    device_name: Ident,
135    generation_type: GenerationType,
136}
137
138enum GenerationType {
139    Dsl(proc_macro2::TokenStream),
140    Manifest(LitStr),
141}
142
143impl syn::parse::Parse for Input {
144    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
145        input.parse::<kw::device_name>()?;
146        input.parse::<syn::Token![:]>()?;
147        let device_name = input.parse()?;
148        input.parse::<syn::Token![,]>()?;
149
150        let look = input.lookahead1();
151
152        if look.peek(kw::dsl) {
153            input.parse::<kw::dsl>()?;
154            input.parse::<syn::Token![:]>()?;
155
156            let braced;
157            braced!(braced in input);
158
159            let tokens = braced.parse()?;
160
161            Ok(Self {
162                device_name,
163                generation_type: GenerationType::Dsl(tokens),
164            })
165        } else if look.peek(kw::manifest) {
166            input.parse::<kw::manifest>()?;
167            input.parse::<syn::Token![:]>()?;
168
169            let path = input.parse()?;
170
171            Ok(Self {
172                device_name,
173                generation_type: GenerationType::Manifest(path),
174            })
175        } else {
176            Err(look.error())
177        }
178    }
179}
180
181mod kw {
182    syn::custom_keyword!(device_name);
183    syn::custom_keyword!(dsl);
184    syn::custom_keyword!(manifest);
185}