1extern crate proc_macro;
2
3use cu29_intern_strs::intern_string;
4use cu29_log::CuLogLevel;
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8use syn::parse::Parser;
9use syn::spanned::Spanned;
10use syn::Token;
11use syn::{Expr, ExprLit, Lit};
12
13#[allow(unused)]
16fn reference_unused_variables(input: TokenStream) -> TokenStream {
17    let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
20    if let Ok(exprs) = parser.parse(input.clone()) {
21        let mut var_usages = Vec::new();
22        for expr in exprs.iter().skip(1) {
25            match expr {
26                syn::Expr::Assign(assign_expr) => {
29                    let value_expr = &assign_expr.right;
30                    var_usages.push(quote::quote! { let _ = &#value_expr; });
31                }
32                _ => {
34                    var_usages.push(quote::quote! { let _ = &#expr; });
35                }
36            }
37        }
38        return quote::quote! { { #(#var_usages;)* } }.into();
42    }
43
44    TokenStream::new()
47}
48
49#[allow(unused)]
54fn create_log_entry(input: TokenStream, level: CuLogLevel) -> TokenStream {
55    use quote::quote;
56    use syn::{Expr, ExprAssign, ExprLit, Lit, Token};
57
58    let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated;
59    let exprs = parser.parse(input).expect("Failed to parse input");
60    let mut exprs_iter = exprs.iter();
61
62    #[cfg(not(feature = "std"))]
63    const STD: bool = false;
64    #[cfg(feature = "std")]
65    const STD: bool = true;
66
67    let msg_expr = exprs_iter.next().expect("Expected at least one expression");
68    let (index, msg_str) = if let Expr::Lit(ExprLit {
69        lit: Lit::Str(msg), ..
70    }) = msg_expr
71    {
72        let s = msg.value();
73        let index = intern_string(&s).expect("Failed to insert log string.");
74        (index, s)
75    } else {
76        panic!("The first parameter of the argument needs to be a string literal.");
77    };
78
79    let level_ident = match level {
80        CuLogLevel::Debug => quote! { Debug },
81        CuLogLevel::Info => quote! { Info },
82        CuLogLevel::Warning => quote! { Warning },
83        CuLogLevel::Error => quote! { Error },
84        CuLogLevel::Critical => quote! { Critical },
85    };
86
87    let mut unnamed_params = Vec::<&Expr>::new();
89    let mut named_params = Vec::<(&Expr, &Expr)>::new();
90
91    for expr in exprs_iter {
92        if let Expr::Assign(ExprAssign { left, right, .. }) = expr {
93            named_params.push((left, right));
94        } else {
95            unnamed_params.push(expr);
96        }
97    }
98
99    let unnamed_prints = unnamed_params.iter().map(|value| {
101        quote! {
102            let param = to_value(#value).expect("Failed to convert a parameter to a Value");
103            log_entry.add_param(ANONYMOUS, param);
104        }
105    });
106
107    let named_prints = named_params.iter().map(|(name, value)| {
108        let idx = intern_string(quote!(#name).to_string().as_str())
109            .expect("Failed to insert log string.");
110        quote! {
111            let param = to_value(#value).expect("Failed to convert a parameter to a Value");
112            log_entry.add_param(#idx, param);
113        }
114    });
115
116    let defmt_fmt_lit = {
119        let mut s = msg_str.clone();
120        if !unnamed_params.is_empty() || !named_params.is_empty() {
121            s.push_str(" |");
122        }
123        for (i, _) in unnamed_params.iter().enumerate() {
124            use std::fmt::Write as _;
125            let _ = write!(&mut s, " arg{}={:?}", i, ());
126        }
127        for (name, _) in named_params.iter() {
128            let name_str = quote!(#name).to_string();
129            s.push(' ');
130            s.push_str(&name_str);
131            s.push_str("={:?}");
132        }
133        syn::LitStr::new(&s, msg_expr.span())
134    };
135
136    let defmt_args_unnamed_ts: Vec<TokenStream2> =
137        unnamed_params.iter().map(|e| quote! { #e }).collect();
138    let defmt_args_named_ts: Vec<TokenStream2> = named_params
139        .iter()
140        .map(|(_, rhs)| quote! { #rhs })
141        .collect();
142
143    #[cfg(not(debug_assertions))]
145    let log_stmt = quote! { let r = log(&mut log_entry); };
146
147    #[cfg(debug_assertions)]
148    let log_stmt: TokenStream2 = {
149        let keys: Vec<_> = named_params
150            .iter()
151            .map(|(name, _)| {
152                let name_str = quote!(#name).to_string();
153                syn::LitStr::new(&name_str, name.span())
154            })
155            .collect();
156        quote! {
157            let r = log_debug_mode(&mut log_entry, #msg_str, &[#(#keys),*]);
158        }
159    };
160
161    let error_handling: Option<TokenStream2> = if STD {
162        Some(quote! {
163            if let Err(e) = r {
164                eprintln!("Warning: Failed to log: {}", e);
165                let backtrace = std::backtrace::Backtrace::capture();
166                eprintln!("{:?}", backtrace);
167            }
168        })
169    } else {
170        None
171    };
172
173    #[cfg(debug_assertions)]
174    let defmt_macro: TokenStream2 = match level {
175        CuLogLevel::Debug => quote! { ::cu29::prelude::__cu29_defmt_debug },
176        CuLogLevel::Info => quote! { ::cu29::prelude::__cu29_defmt_info  },
177        CuLogLevel::Warning => quote! { ::cu29::prelude::__cu29_defmt_warn  },
178        CuLogLevel::Error => quote! { ::cu29::prelude::__cu29_defmt_error },
179        CuLogLevel::Critical => quote! { ::cu29::prelude::__cu29_defmt_error },
180    };
181
182    #[cfg(debug_assertions)]
183    let maybe_inject_defmt: Option<TokenStream2> = if STD {
184        None } else {
186        Some(quote! {
187             #[cfg(debug_assertions)]
188             {
189                 #defmt_macro!(#defmt_fmt_lit, #(#defmt_args_unnamed_ts,)* #(#defmt_args_named_ts,)*);
190             }
191        })
192    };
193
194    #[cfg(not(debug_assertions))]
195    let maybe_inject_defmt: Option<TokenStream2> = None; quote! {{
199        let mut log_entry = CuLogEntry::new(#index, CuLogLevel::#level_ident);
200        #(#unnamed_prints)*
201        #(#named_prints)*
202
203        #maybe_inject_defmt
204
205        #log_stmt
206        #error_handling
207    }}
208    .into()
209}
210
211#[cfg(any(feature = "log-level-debug", cu29_default_log_level_debug))]
234#[proc_macro]
235pub fn debug(input: TokenStream) -> TokenStream {
236    create_log_entry(input, CuLogLevel::Debug)
237}
238
239#[cfg(any(feature = "log-level-debug", feature = "log-level-info",))]
246#[proc_macro]
247pub fn info(input: TokenStream) -> TokenStream {
248    create_log_entry(input, CuLogLevel::Info)
249}
250
251#[cfg(any(
258    feature = "log-level-debug",
259    feature = "log-level-info",
260    feature = "log-level-warning",
261))]
262#[proc_macro]
263pub fn warning(input: TokenStream) -> TokenStream {
264    create_log_entry(input, CuLogLevel::Warning)
265}
266
267#[cfg(any(
274    feature = "log-level-debug",
275    feature = "log-level-info",
276    feature = "log-level-warning",
277    feature = "log-level-error",
278))]
279#[proc_macro]
280pub fn error(input: TokenStream) -> TokenStream {
281    create_log_entry(input, CuLogLevel::Error)
282}
283
284#[cfg(any(
291    feature = "log-level-debug",
292    feature = "log-level-info",
293    feature = "log-level-warning",
294    feature = "log-level-error",
295    feature = "log-level-critical",
296))]
297#[proc_macro]
298pub fn critical(input: TokenStream) -> TokenStream {
299    create_log_entry(input, CuLogLevel::Critical)
300}
301
302#[cfg(not(any(feature = "log-level-debug", cu29_default_log_level_debug)))]
304#[proc_macro]
305pub fn debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
306    reference_unused_variables(input)
307}
308
309#[cfg(not(any(feature = "log-level-debug", feature = "log-level-info",)))]
310#[proc_macro]
311pub fn info(input: TokenStream) -> TokenStream {
312    reference_unused_variables(input)
313}
314
315#[cfg(not(any(
316    feature = "log-level-debug",
317    feature = "log-level-info",
318    feature = "log-level-warning",
319)))]
320#[proc_macro]
321pub fn warning(input: TokenStream) -> TokenStream {
322    reference_unused_variables(input)
323}
324
325#[cfg(not(any(
326    feature = "log-level-debug",
327    feature = "log-level-info",
328    feature = "log-level-warning",
329    feature = "log-level-error",
330)))]
331#[proc_macro]
332pub fn error(input: TokenStream) -> TokenStream {
333    reference_unused_variables(input)
334}
335
336#[cfg(not(any(
337    feature = "log-level-debug",
338    feature = "log-level-info",
339    feature = "log-level-warning",
340    feature = "log-level-error",
341    feature = "log-level-critical",
342)))]
343#[proc_macro]
344pub fn critical(input: TokenStream) -> TokenStream {
345    reference_unused_variables(input)
346}
347
348#[proc_macro]
355pub fn intern(input: TokenStream) -> TokenStream {
356    let expr = syn::parse::<Expr>(input).expect("Failed to parse input as expression");
357    let (index, _msg) = if let Expr::Lit(ExprLit {
358        lit: Lit::Str(msg), ..
359    }) = expr
360    {
361        let msg = msg.value();
362        let index = intern_string(&msg).expect("Failed to insert log string.");
363        (index, msg)
364    } else {
365        panic!("The first parameter of the argument needs to be a string literal.");
366    };
367    quote! { #index }.into()
368}