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 = match parser.parse(input) {
66 Ok(exprs) => exprs,
67 Err(err) => return err.to_compile_error().into(),
68 };
69 let mut exprs_iter = exprs.iter();
70
71 #[cfg(not(feature = "std"))]
72 const STD: bool = false;
73 #[cfg(feature = "std")]
74 const STD: bool = true;
75
76 let msg_expr = match exprs_iter.next() {
77 Some(expr) => expr,
78 None => {
79 return syn::Error::new(
80 proc_macro2::Span::call_site(),
81 "Expected at least one expression",
82 )
83 .to_compile_error()
84 .into();
85 }
86 };
87 let (index, msg_str) = if let Expr::Lit(ExprLit {
88 lit: Lit::Str(msg), ..
89 }) = msg_expr
90 {
91 let s = msg.value();
92 let index = match intern_string(&s) {
93 Some(index) => index,
94 None => {
95 return syn::Error::new_spanned(msg_expr, "Failed to intern log string.")
96 .to_compile_error()
97 .into();
98 }
99 };
100 (index, s)
101 } else {
102 return syn::Error::new_spanned(
103 msg_expr,
104 "The first parameter of the argument needs to be a string literal.",
105 )
106 .to_compile_error()
107 .into();
108 };
109
110 let level_ident = match level {
111 CuLogLevel::Debug => quote! { Debug },
112 CuLogLevel::Info => quote! { Info },
113 CuLogLevel::Warning => quote! { Warning },
114 CuLogLevel::Error => quote! { Error },
115 CuLogLevel::Critical => quote! { Critical },
116 };
117
118 let mut unnamed_params = Vec::<&Expr>::new();
120 let mut named_params = Vec::<(&Expr, &Expr)>::new();
121
122 for expr in exprs_iter {
123 if let Expr::Assign(ExprAssign { left, right, .. }) = expr {
124 named_params.push((left, right));
125 } else {
126 unnamed_params.push(expr);
127 }
128 }
129
130 let unnamed_prints = unnamed_params.iter().map(|value| {
132 quote! {
133 let param = to_value(#value).expect("Failed to convert a parameter to a Value");
134 log_entry.add_param(ANONYMOUS, param);
135 }
136 });
137
138 let mut named_prints = Vec::with_capacity(named_params.len());
139 for (name, value) in &named_params {
140 let name_str = quote!(#name).to_string();
141 let idx = match intern_string(&name_str) {
142 Some(idx) => idx,
143 None => {
144 return syn::Error::new_spanned(name, "Failed to intern log parameter name.")
145 .to_compile_error()
146 .into();
147 }
148 };
149 named_prints.push(quote! {
150 let param = to_value(#value).expect("Failed to convert a parameter to a Value");
151 log_entry.add_param(#idx, param);
152 });
153 }
154
155 #[cfg(feature = "textlogs")]
158 let (defmt_fmt_lit, defmt_args_unnamed_ts, defmt_args_named_ts, defmt_available) = {
159 let defmt_fmt_lit = {
160 let mut s = msg_str.clone();
161 if !named_params.is_empty() {
162 s.push_str(" |");
163 }
164 for (name, _) in named_params.iter() {
165 let name_str = quote!(#name).to_string();
166 s.push(' ');
167 s.push_str(&name_str);
168 s.push_str("={:?}");
169 }
170 syn::LitStr::new(&s, msg_expr.span())
171 };
172
173 let defmt_args_unnamed_ts: Vec<TokenStream2> =
174 unnamed_params.iter().map(|e| quote! { #e }).collect();
175 let defmt_args_named_ts: Vec<TokenStream2> = named_params
176 .iter()
177 .map(|(_, rhs)| quote! { #rhs })
178 .collect();
179
180 let defmt_available = crate_name("defmt").is_ok();
181
182 (
183 defmt_fmt_lit,
184 defmt_args_unnamed_ts,
185 defmt_args_named_ts,
186 defmt_available,
187 )
188 };
189
190 #[cfg(feature = "textlogs")]
191 fn defmt_macro_path(level: CuLogLevel) -> TokenStream2 {
192 let macro_ident = match level {
193 CuLogLevel::Debug => quote! { defmt_debug },
194 CuLogLevel::Info => quote! { defmt_info },
195 CuLogLevel::Warning => quote! { defmt_warn },
196 CuLogLevel::Error => quote! { defmt_error },
197 CuLogLevel::Critical => quote! { defmt_error },
198 };
199
200 let (base, use_prelude) = match crate_name("cu29-log") {
201 Ok(FoundCrate::Name(name)) => {
202 let ident = proc_macro2::Ident::new(&name, Span::call_site());
203 (quote! { ::#ident }, false)
204 }
205 Ok(FoundCrate::Itself) => (quote! { crate }, false),
206 Err(_) => match crate_name("cu29") {
207 Ok(FoundCrate::Name(name)) => {
208 let ident = proc_macro2::Ident::new(&name, Span::call_site());
209 (quote! { ::#ident }, true)
210 }
211 Ok(FoundCrate::Itself) => (quote! { crate }, true),
212 Err(_) => (quote! { ::cu29_log }, false),
213 },
214 };
215
216 if use_prelude {
217 quote! { #base::prelude::#macro_ident }
218 } else {
219 quote! { #base::#macro_ident }
220 }
221 }
222
223 #[cfg(not(debug_assertions))]
225 let log_stmt = quote! { let r = log(&mut log_entry); };
226
227 #[cfg(debug_assertions)]
228 let log_stmt: TokenStream2 = {
229 let keys: Vec<_> = named_params
230 .iter()
231 .map(|(name, _)| {
232 let name_str = quote!(#name).to_string();
233 syn::LitStr::new(&name_str, name.span())
234 })
235 .collect();
236 quote! {
237 let r = log_debug_mode(&mut log_entry, #msg_str, &[#(#keys),*]);
238 }
239 };
240
241 let error_handling: Option<TokenStream2> = Some(quote! {
242 if let Err(_e) = r {
243 let _ = &_e;
244 }
245 });
246
247 #[cfg(feature = "textlogs")]
248 let defmt_macro: TokenStream2 = defmt_macro_path(level);
249
250 #[cfg(feature = "textlogs")]
251 let maybe_inject_defmt: Option<TokenStream2> = if STD || !defmt_available {
252 None } else {
254 Some(quote! {
255 #defmt_macro!(#defmt_fmt_lit, #(#defmt_args_unnamed_ts,)* #(#defmt_args_named_ts,)*);
256 })
257 };
258
259 #[cfg(not(feature = "textlogs"))]
260 let maybe_inject_defmt: Option<TokenStream2> = None; quote! {{
264 let mut log_entry = CuLogEntry::new(#index, CuLogLevel::#level_ident);
265 #(#unnamed_prints)*
266 #(#named_prints)*
267
268 #maybe_inject_defmt
269
270 #log_stmt
271 #error_handling
272 }}
273 .into()
274}
275
276#[cfg(any(feature = "log-level-debug", cu29_default_log_level_debug))]
299#[proc_macro]
300pub fn debug(input: TokenStream) -> TokenStream {
301 create_log_entry(input, CuLogLevel::Debug)
302}
303
304#[cfg(any(feature = "log-level-debug", feature = "log-level-info",))]
311#[proc_macro]
312pub fn info(input: TokenStream) -> TokenStream {
313 create_log_entry(input, CuLogLevel::Info)
314}
315
316#[cfg(any(
323 feature = "log-level-debug",
324 feature = "log-level-info",
325 feature = "log-level-warning",
326))]
327#[proc_macro]
328pub fn warning(input: TokenStream) -> TokenStream {
329 create_log_entry(input, CuLogLevel::Warning)
330}
331
332#[cfg(any(
339 feature = "log-level-debug",
340 feature = "log-level-info",
341 feature = "log-level-warning",
342 feature = "log-level-error",
343))]
344#[proc_macro]
345pub fn error(input: TokenStream) -> TokenStream {
346 create_log_entry(input, CuLogLevel::Error)
347}
348
349#[cfg(any(
356 feature = "log-level-debug",
357 feature = "log-level-info",
358 feature = "log-level-warning",
359 feature = "log-level-error",
360 feature = "log-level-critical",
361))]
362#[proc_macro]
363pub fn critical(input: TokenStream) -> TokenStream {
364 create_log_entry(input, CuLogLevel::Critical)
365}
366
367#[cfg(not(any(feature = "log-level-debug", cu29_default_log_level_debug)))]
369#[proc_macro]
370pub fn debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
371 reference_unused_variables(input)
372}
373
374#[cfg(not(any(feature = "log-level-debug", feature = "log-level-info",)))]
375#[proc_macro]
376pub fn info(input: TokenStream) -> TokenStream {
377 reference_unused_variables(input)
378}
379
380#[cfg(not(any(
381 feature = "log-level-debug",
382 feature = "log-level-info",
383 feature = "log-level-warning",
384)))]
385#[proc_macro]
386pub fn warning(input: TokenStream) -> TokenStream {
387 reference_unused_variables(input)
388}
389
390#[cfg(not(any(
391 feature = "log-level-debug",
392 feature = "log-level-info",
393 feature = "log-level-warning",
394 feature = "log-level-error",
395)))]
396#[proc_macro]
397pub fn error(input: TokenStream) -> TokenStream {
398 reference_unused_variables(input)
399}
400
401#[cfg(not(any(
402 feature = "log-level-debug",
403 feature = "log-level-info",
404 feature = "log-level-warning",
405 feature = "log-level-error",
406 feature = "log-level-critical",
407)))]
408#[proc_macro]
409pub fn critical(input: TokenStream) -> TokenStream {
410 reference_unused_variables(input)
411}
412
413#[proc_macro]
420pub fn intern(input: TokenStream) -> TokenStream {
421 let expr = match syn::parse::<Expr>(input) {
422 Ok(expr) => expr,
423 Err(err) => return err.to_compile_error().into(),
424 };
425 let index = if let Expr::Lit(ExprLit {
426 lit: Lit::Str(msg), ..
427 }) = &expr
428 {
429 let msg = msg.value();
430 match intern_string(&msg) {
431 Some(index) => index,
432 None => {
433 return syn::Error::new_spanned(&expr, "Failed to intern log string.")
434 .to_compile_error()
435 .into();
436 }
437 }
438 } else {
439 return syn::Error::new_spanned(
440 &expr,
441 "The first parameter of the argument needs to be a string literal.",
442 )
443 .to_compile_error()
444 .into();
445 };
446 quote! { #index }.into()
447}