device_driver_macros/
lib.rs1#![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#[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}