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