1use defmt_parser::ParserMode;
2use proc_macro::TokenStream;
3use proc_macro2::TokenStream as TokenStream2;
4use proc_macro_error2::abort;
5use quote::quote;
6use syn::{parse_macro_input, parse_quote};
7
8use crate::construct;
9use crate::function_like::log::{Args, Codegen};
10
11pub(crate) fn expand(args: TokenStream) -> TokenStream {
12 expand_parsed(args:parse_macro_input!(args as Args)).into()
13}
14
15pub(crate) fn expand_parsed(args: Args) -> TokenStream2 {
16 let format_string = args.format_string.value();
17 let fragments = match defmt_parser::parse(&format_string, ParserMode::Strict) {
18 Ok(args) => args,
19 Err(e @ defmt_parser::Error::UnknownDisplayHint(_)) => abort!(
20 args.format_string, "{}", e;
21 help = "`defmt` uses a slightly different syntax than regular formatting in Rust. See https://defmt.ferrous-systems.com/macros.html for more details.";
22 ),
23 Err(e) => abort!(args.format_string, "{}", e), // No extra help
24 };
25
26 let formatting_exprs = args
27 .formatting_args
28 .map(|punctuated| punctuated.into_iter().collect::<Vec<_>>())
29 .unwrap_or_default();
30
31 let Codegen { patterns, exprs } = Codegen::new(
32 &fragments,
33 formatting_exprs.len(),
34 args.format_string.span(),
35 );
36
37 let header =
38 construct::interned_string(&format_string, "println", true, None, &parse_quote!(defmt));
39 let content = if exprs.is_empty() {
40 quote!(
41 defmt::export::acquire_header_and_release(&#header);
42 )
43 } else {
44 quote!(
45 // safety: will be released a few lines further down
46 unsafe { defmt::export::acquire_and_header(&#header); };
47 #(#exprs;)*
48 // safety: acquire() was called a few lines above
49 unsafe { defmt::export::release() }
50 )
51 };
52 quote!({
53 match (#(&(#formatting_exprs)),*) {
54 (#(#patterns),*) => {
55 #content
56 }
57 }
58 })
59}
60