1extern crate proc_macro;
2
3use cu29_intern_strs::intern_string;
4use cu29_log::CuLogLevel;
5use proc_macro::TokenStream;
6#[cfg(feature = "textlogs")]
7use proc_macro_crate::{FoundCrate, crate_name};
8#[cfg(feature = "textlogs")]
9use proc_macro2::Span;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::quote;
12#[allow(unused)]
13use syn::Token;
14use syn::parse::Parser;
15#[cfg(any(feature = "textlogs", debug_assertions))]
16use syn::spanned::Spanned;
17use syn::{Expr, ExprLit, Lit};
18
19#[allow(unused)]
22fn reference_unused_variables(input: TokenStream) -> TokenStream {
23 let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
26 if let Ok(exprs) = parser.parse(input.clone()) {
27 let mut var_usages = Vec::new();
28 for expr in exprs.iter().skip(1) {
31 match expr {
32 syn::Expr::Assign(assign_expr) => {
35 let value_expr = &assign_expr.right;
36 var_usages.push(quote::quote! { let _ = &#value_expr; });
37 }
38 _ => {
40 var_usages.push(quote::quote! { let _ = &#expr; });
41 }
42 }
43 }
44 return quote::quote! { { #(#var_usages;)* } }.into();
48 }
49
50 TokenStream::new()
53}
54
55#[allow(unused)]
60fn create_log_entry(input: TokenStream, level: CuLogLevel) -> TokenStream {
61 use quote::quote;
62 use syn::{Expr, ExprAssign, ExprLit, Lit, Token};
63
64 let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated;
65 let exprs = parser.parse(input).expect("Failed to parse input");
66 let mut exprs_iter = exprs.iter();
67
68 #[cfg(not(feature = "std"))]
69 const STD: bool = false;
70 #[cfg(feature = "std")]
71 const STD: bool = true;
72
73 let msg_expr = exprs_iter.next().expect("Expected at least one expression");
74 let (index, msg_str) = if let Expr::Lit(ExprLit {
75 lit: Lit::Str(msg), ..
76 }) = msg_expr
77 {
78 let s = msg.value();
79 let index = intern_string(&s).expect("Failed to insert log string.");
80 (index, s)
81 } else {
82 panic!("The first parameter of the argument needs to be a string literal.");
83 };
84
85 let level_ident = match level {
86 CuLogLevel::Debug => quote! { Debug },
87 CuLogLevel::Info => quote! { Info },
88 CuLogLevel::Warning => quote! { Warning },
89 CuLogLevel::Error => quote! { Error },
90 CuLogLevel::Critical => quote! { Critical },
91 };
92
93 let mut unnamed_params = Vec::<&Expr>::new();
95 let mut named_params = Vec::<(&Expr, &Expr)>::new();
96
97 for expr in exprs_iter {
98 if let Expr::Assign(ExprAssign { left, right, .. }) = expr {
99 named_params.push((left, right));
100 } else {
101 unnamed_params.push(expr);
102 }
103 }
104
105 let unnamed_prints = unnamed_params.iter().map(|value| {
107 quote! {
108 let param = to_value(#value).expect("Failed to convert a parameter to a Value");
109 log_entry.add_param(ANONYMOUS, param);
110 }
111 });
112
113 let named_prints = named_params.iter().map(|(name, value)| {
114 let idx = intern_string(quote!(#name).to_string().as_str())
115 .expect("Failed to insert log string.");
116 quote! {
117 let param = to_value(#value).expect("Failed to convert a parameter to a Value");
118 log_entry.add_param(#idx, param);
119 }
120 });
121
122 #[cfg(feature = "textlogs")]
125 let (defmt_fmt_lit, defmt_args_unnamed_ts, defmt_args_named_ts, defmt_available) = {
126 let defmt_fmt_lit = {
127 let mut s = msg_str.clone();
128 if !named_params.is_empty() {
129 s.push_str(" |");
130 }
131 for (name, _) in named_params.iter() {
132 let name_str = quote!(#name).to_string();
133 s.push(' ');
134 s.push_str(&name_str);
135 s.push_str("={:?}");
136 }
137 syn::LitStr::new(&s, msg_expr.span())
138 };
139
140 let defmt_args_unnamed_ts: Vec<TokenStream2> =
141 unnamed_params.iter().map(|e| quote! { #e }).collect();
142 let defmt_args_named_ts: Vec<TokenStream2> = named_params
143 .iter()
144 .map(|(_, rhs)| quote! { #rhs })
145 .collect();
146
147 let defmt_available = crate_name("defmt").is_ok();
148
149 (
150 defmt_fmt_lit,
151 defmt_args_unnamed_ts,
152 defmt_args_named_ts,
153 defmt_available,
154 )
155 };
156
157 #[cfg(feature = "textlogs")]
158 fn defmt_macro_path(level: CuLogLevel) -> TokenStream2 {
159 let macro_ident = match level {
160 CuLogLevel::Debug => quote! { defmt_debug },
161 CuLogLevel::Info => quote! { defmt_info },
162 CuLogLevel::Warning => quote! { defmt_warn },
163 CuLogLevel::Error => quote! { defmt_error },
164 CuLogLevel::Critical => quote! { defmt_error },
165 };
166
167 let (base, use_prelude) = match crate_name("cu29") {
168 Ok(FoundCrate::Name(name)) => {
169 let ident = proc_macro2::Ident::new(&name, Span::call_site());
170 (quote! { ::#ident }, true)
171 }
172 Ok(FoundCrate::Itself) => (quote! { crate }, true),
173 Err(_) => match crate_name("cu29-log") {
174 Ok(FoundCrate::Name(name)) => {
175 let ident = proc_macro2::Ident::new(&name, Span::call_site());
176 (quote! { ::#ident }, false)
177 }
178 Ok(FoundCrate::Itself) => (quote! { crate }, false),
179 Err(_) => (quote! { ::cu29_log }, false),
180 },
181 };
182
183 if use_prelude {
184 quote! { #base::prelude::#macro_ident }
185 } else {
186 quote! { #base::#macro_ident }
187 }
188 }
189
190 #[cfg(not(debug_assertions))]
192 let log_stmt = quote! { let r = log(&mut log_entry); };
193
194 #[cfg(debug_assertions)]
195 let log_stmt: TokenStream2 = {
196 let keys: Vec<_> = named_params
197 .iter()
198 .map(|(name, _)| {
199 let name_str = quote!(#name).to_string();
200 syn::LitStr::new(&name_str, name.span())
201 })
202 .collect();
203 quote! {
204 let r = log_debug_mode(&mut log_entry, #msg_str, &[#(#keys),*]);
205 }
206 };
207
208 let error_handling: Option<TokenStream2> = Some(quote! {
209 if let Err(_e) = r {
210 let _ = &_e;
211 }
212 });
213
214 #[cfg(feature = "textlogs")]
215 let defmt_macro: TokenStream2 = defmt_macro_path(level);
216
217 #[cfg(feature = "textlogs")]
218 let maybe_inject_defmt: Option<TokenStream2> = if STD || !defmt_available {
219 None } else {
221 Some(quote! {
222 #defmt_macro!(#defmt_fmt_lit, #(#defmt_args_unnamed_ts,)* #(#defmt_args_named_ts,)*);
223 })
224 };
225
226 #[cfg(not(feature = "textlogs"))]
227 let maybe_inject_defmt: Option<TokenStream2> = None; quote! {{
231 let mut log_entry = CuLogEntry::new(#index, CuLogLevel::#level_ident);
232 #(#unnamed_prints)*
233 #(#named_prints)*
234
235 #maybe_inject_defmt
236
237 #log_stmt
238 #error_handling
239 }}
240 .into()
241}
242
243#[cfg(any(feature = "log-level-debug", cu29_default_log_level_debug))]
266#[proc_macro]
267pub fn debug(input: TokenStream) -> TokenStream {
268 create_log_entry(input, CuLogLevel::Debug)
269}
270
271#[cfg(any(feature = "log-level-debug", feature = "log-level-info",))]
278#[proc_macro]
279pub fn info(input: TokenStream) -> TokenStream {
280 create_log_entry(input, CuLogLevel::Info)
281}
282
283#[cfg(any(
290 feature = "log-level-debug",
291 feature = "log-level-info",
292 feature = "log-level-warning",
293))]
294#[proc_macro]
295pub fn warning(input: TokenStream) -> TokenStream {
296 create_log_entry(input, CuLogLevel::Warning)
297}
298
299#[cfg(any(
306 feature = "log-level-debug",
307 feature = "log-level-info",
308 feature = "log-level-warning",
309 feature = "log-level-error",
310))]
311#[proc_macro]
312pub fn error(input: TokenStream) -> TokenStream {
313 create_log_entry(input, CuLogLevel::Error)
314}
315
316#[cfg(any(
323 feature = "log-level-debug",
324 feature = "log-level-info",
325 feature = "log-level-warning",
326 feature = "log-level-error",
327 feature = "log-level-critical",
328))]
329#[proc_macro]
330pub fn critical(input: TokenStream) -> TokenStream {
331 create_log_entry(input, CuLogLevel::Critical)
332}
333
334#[cfg(not(any(feature = "log-level-debug", cu29_default_log_level_debug)))]
336#[proc_macro]
337pub fn debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
338 reference_unused_variables(input)
339}
340
341#[cfg(not(any(feature = "log-level-debug", feature = "log-level-info",)))]
342#[proc_macro]
343pub fn info(input: TokenStream) -> TokenStream {
344 reference_unused_variables(input)
345}
346
347#[cfg(not(any(
348 feature = "log-level-debug",
349 feature = "log-level-info",
350 feature = "log-level-warning",
351)))]
352#[proc_macro]
353pub fn warning(input: TokenStream) -> TokenStream {
354 reference_unused_variables(input)
355}
356
357#[cfg(not(any(
358 feature = "log-level-debug",
359 feature = "log-level-info",
360 feature = "log-level-warning",
361 feature = "log-level-error",
362)))]
363#[proc_macro]
364pub fn error(input: TokenStream) -> TokenStream {
365 reference_unused_variables(input)
366}
367
368#[cfg(not(any(
369 feature = "log-level-debug",
370 feature = "log-level-info",
371 feature = "log-level-warning",
372 feature = "log-level-error",
373 feature = "log-level-critical",
374)))]
375#[proc_macro]
376pub fn critical(input: TokenStream) -> TokenStream {
377 reference_unused_variables(input)
378}
379
380#[proc_macro]
387pub fn intern(input: TokenStream) -> TokenStream {
388 let expr = syn::parse::<Expr>(input).expect("Failed to parse input as expression");
389 let (index, _msg) = if let Expr::Lit(ExprLit {
390 lit: Lit::Str(msg), ..
391 }) = expr
392 {
393 let msg = msg.value();
394 let index = intern_string(&msg).expect("Failed to insert log string.");
395 (index, msg)
396 } else {
397 panic!("The first parameter of the argument needs to be a string literal.");
398 };
399 quote! { #index }.into()
400}