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;
8#[allow(unused)]
9use syn::Token;
10use syn::parse::Parser;
11use syn::spanned::Spanned;
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> = Some(quote! {
163 if let Err(_e) = r {
164 let _ = &_e;
165 }
166 });
167
168 #[cfg(debug_assertions)]
169 let defmt_macro: TokenStream2 = match level {
170 CuLogLevel::Debug => quote! { ::cu29::prelude::__cu29_defmt_debug },
171 CuLogLevel::Info => quote! { ::cu29::prelude::__cu29_defmt_info },
172 CuLogLevel::Warning => quote! { ::cu29::prelude::__cu29_defmt_warn },
173 CuLogLevel::Error => quote! { ::cu29::prelude::__cu29_defmt_error },
174 CuLogLevel::Critical => quote! { ::cu29::prelude::__cu29_defmt_error },
175 };
176
177 #[cfg(debug_assertions)]
178 let maybe_inject_defmt: Option<TokenStream2> = if STD {
179 None } else {
181 Some(quote! {
182 #[cfg(debug_assertions)]
183 {
184 #defmt_macro!(#defmt_fmt_lit, #(#defmt_args_unnamed_ts,)* #(#defmt_args_named_ts,)*);
185 }
186 })
187 };
188
189 #[cfg(not(debug_assertions))]
190 let maybe_inject_defmt: Option<TokenStream2> = None; quote! {{
194 let mut log_entry = CuLogEntry::new(#index, CuLogLevel::#level_ident);
195 #(#unnamed_prints)*
196 #(#named_prints)*
197
198 #maybe_inject_defmt
199
200 #log_stmt
201 #error_handling
202 }}
203 .into()
204}
205
206#[cfg(any(feature = "log-level-debug", cu29_default_log_level_debug))]
229#[proc_macro]
230pub fn debug(input: TokenStream) -> TokenStream {
231 create_log_entry(input, CuLogLevel::Debug)
232}
233
234#[cfg(any(feature = "log-level-debug", feature = "log-level-info",))]
241#[proc_macro]
242pub fn info(input: TokenStream) -> TokenStream {
243 create_log_entry(input, CuLogLevel::Info)
244}
245
246#[cfg(any(
253 feature = "log-level-debug",
254 feature = "log-level-info",
255 feature = "log-level-warning",
256))]
257#[proc_macro]
258pub fn warning(input: TokenStream) -> TokenStream {
259 create_log_entry(input, CuLogLevel::Warning)
260}
261
262#[cfg(any(
269 feature = "log-level-debug",
270 feature = "log-level-info",
271 feature = "log-level-warning",
272 feature = "log-level-error",
273))]
274#[proc_macro]
275pub fn error(input: TokenStream) -> TokenStream {
276 create_log_entry(input, CuLogLevel::Error)
277}
278
279#[cfg(any(
286 feature = "log-level-debug",
287 feature = "log-level-info",
288 feature = "log-level-warning",
289 feature = "log-level-error",
290 feature = "log-level-critical",
291))]
292#[proc_macro]
293pub fn critical(input: TokenStream) -> TokenStream {
294 create_log_entry(input, CuLogLevel::Critical)
295}
296
297#[cfg(not(any(feature = "log-level-debug", cu29_default_log_level_debug)))]
299#[proc_macro]
300pub fn debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
301 reference_unused_variables(input)
302}
303
304#[cfg(not(any(feature = "log-level-debug", feature = "log-level-info",)))]
305#[proc_macro]
306pub fn info(input: TokenStream) -> TokenStream {
307 reference_unused_variables(input)
308}
309
310#[cfg(not(any(
311 feature = "log-level-debug",
312 feature = "log-level-info",
313 feature = "log-level-warning",
314)))]
315#[proc_macro]
316pub fn warning(input: TokenStream) -> TokenStream {
317 reference_unused_variables(input)
318}
319
320#[cfg(not(any(
321 feature = "log-level-debug",
322 feature = "log-level-info",
323 feature = "log-level-warning",
324 feature = "log-level-error",
325)))]
326#[proc_macro]
327pub fn error(input: TokenStream) -> TokenStream {
328 reference_unused_variables(input)
329}
330
331#[cfg(not(any(
332 feature = "log-level-debug",
333 feature = "log-level-info",
334 feature = "log-level-warning",
335 feature = "log-level-error",
336 feature = "log-level-critical",
337)))]
338#[proc_macro]
339pub fn critical(input: TokenStream) -> TokenStream {
340 reference_unused_variables(input)
341}
342
343#[proc_macro]
350pub fn intern(input: TokenStream) -> TokenStream {
351 let expr = syn::parse::<Expr>(input).expect("Failed to parse input as expression");
352 let (index, _msg) = if let Expr::Lit(ExprLit {
353 lit: Lit::Str(msg), ..
354 }) = expr
355 {
356 let msg = msg.value();
357 let index = intern_string(&msg).expect("Failed to insert log string.");
358 (index, msg)
359 } else {
360 panic!("The first parameter of the argument needs to be a string literal.");
361 };
362 quote! { #index }.into()
363}