Skip to main content

device_driver_macros/
lib.rs

1#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
2
3use std::{
4    fs::File,
5    io::{Read, stderr},
6    path::PathBuf,
7};
8
9use clap::Parser;
10use device_driver_core::{
11    CodegenTarget, CompileOptions, GeneralOptions, MirOptions, RustCodegenOptions,
12};
13use device_driver_diagnostics::{DynError, Metadata, ResultExt};
14use proc_macro::TokenStream;
15use proc_macro2::Span;
16use syn::{LitStr, Token};
17
18/// Macro to implement the device driver.
19///
20/// ## Usage:
21///
22/// Manifest:
23/// ```rust,ignore
24/// # use device_driver_macros::create_device;
25/// compile!(
26///     options: "",
27///     manifest: "path/to/manifest/file.ddsl"
28/// );
29/// ```
30///
31/// Inline ddsl (only used for testing):
32/// ```rust,ignore
33/// # use device_driver_macros::create_device;
34/// compile!(
35///     options: "",
36///     unstable_ddsl: "
37///         // DDSL input
38///     "
39/// );
40/// ```
41#[proc_macro]
42pub fn compile(item: TokenStream) -> TokenStream {
43    let input = match syn::parse::<Input>(item) {
44        Ok(i) => i,
45        Err(e) => return e.into_compile_error().into(),
46    };
47
48    match try_create_device(input) {
49        Ok(tokens) => tokens,
50        Err(e) => syn::Error::new(Span::call_site(), format!("{e:#}"))
51            .into_compile_error()
52            .into(),
53    }
54}
55
56#[derive(Parser, Debug, Clone)]
57#[command(no_binary_name = true)]
58struct MacroCompileOptions {
59    #[command(flatten)]
60    pub general_options: GeneralOptions,
61    #[command(flatten)]
62    pub mir_options: MirOptions,
63    #[command(flatten)]
64    pub rust_codegen_options: RustCodegenOptions,
65}
66
67impl From<MacroCompileOptions> for CompileOptions {
68    fn from(value: MacroCompileOptions) -> Self {
69        Self {
70            general_options: value.general_options,
71            mir_options: value.mir_options,
72            target: CodegenTarget::Rust(value.rust_codegen_options),
73        }
74    }
75}
76
77fn try_create_device(input: Input) -> Result<TokenStream, DynError> {
78    let (source, source_path) = match input.source {
79        Source::Ddsl(source_lit) => (source_lit.value(), source_lit.span().file()),
80        Source::Manifest(path) => {
81            let mut source_path = PathBuf::from(path.value());
82            if source_path.is_relative() {
83                let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
84                source_path = manifest_dir.join(source_path);
85            }
86
87            let mut source = String::new();
88            let mut file = File::open(&source_path).map_err(|e| {
89                DynError::new(format!(
90                    "could not open the manifest file at '{}': {e}",
91                    source_path.display()
92                ))
93            })?;
94            file.read_to_string(&mut source)
95                .with_message(|| "could not read manifest file")?;
96            (source, source_path.display().to_string())
97        }
98    };
99
100    let compile_options = input
101        .compile_options
102        .map(|str| str.value())
103        .unwrap_or_default()
104        .split(' ')
105        .filter(|s| !s.is_empty())
106        .map(String::from)
107        .collect::<Vec<_>>();
108    let compile_options = MacroCompileOptions::try_parse_from(compile_options).into_dyn_result()?;
109    let (output, diagnostics) = device_driver_core::compile(&source, compile_options.into())?;
110
111    diagnostics
112        .print_to(
113            stderr().lock(),
114            Metadata {
115                source: &source,
116                source_path: &source_path,
117                term_width: None,
118                ansi: true,
119                unicode: true,
120                anonymized_line_numbers: false,
121            },
122        )
123        .unwrap();
124    output
125        .parse()
126        .map_err(|e: proc_macro::LexError| DynError::new(e.to_string()))
127        .with_message(|| "could not parse the output")
128}
129
130enum Source {
131    Ddsl(LitStr),
132    Manifest(LitStr),
133}
134
135struct Input {
136    source: Source,
137    compile_options: Option<LitStr>,
138}
139
140impl syn::parse::Parse for Input {
141    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
142        let mut compile_options = None;
143        let mut source: Option<Source> = None;
144
145        let mut first_loop = true;
146
147        loop {
148            if first_loop {
149                first_loop = false;
150            } else {
151                if !input.is_empty() {
152                    input.parse::<Token![,]>()?;
153                }
154            }
155
156            if input.is_empty()
157                && let Some(source) = source
158            {
159                return Ok(Input {
160                    source,
161                    compile_options,
162                });
163            }
164
165            let look = input.lookahead1();
166
167            if compile_options.is_none() && look.peek(kw::options) {
168                input.parse::<kw::options>()?;
169                input.parse::<syn::Token![:]>()?;
170
171                compile_options = Some(input.parse()?);
172            } else if source.is_none() && look.peek(kw::unstable_ddsl) {
173                input.parse::<kw::unstable_ddsl>()?;
174                input.parse::<syn::Token![:]>()?;
175
176                source = Some(Source::Ddsl(input.parse()?));
177            } else if source.is_none() && look.peek(kw::manifest) {
178                input.parse::<kw::manifest>()?;
179                input.parse::<syn::Token![:]>()?;
180
181                source = Some(Source::Manifest(input.parse()?));
182            } else {
183                return Err(look.error());
184            }
185        }
186    }
187}
188
189mod kw {
190    syn::custom_keyword!(options);
191    syn::custom_keyword!(unstable_ddsl);
192    syn::custom_keyword!(manifest);
193}