1extern crate proc_macro;
2
3use cu29_intern_strs::intern_string;
4use cu29_log::CuLogLevel;
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::parse::Parser;
8#[cfg(debug_assertions)]
9use syn::spanned::Spanned;
10use syn::Token;
11use syn::{Expr, ExprAssign, 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 proc_macro::TokenStream::new()
47}
48
49#[allow(unused)]
54fn create_log_entry(input: TokenStream, level: CuLogLevel) -> TokenStream {
55 let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated;
56 let exprs = parser.parse(input).expect("Failed to parse input");
57
58 let mut exprs_iter = exprs.iter();
59
60 let msg_expr = exprs_iter.next().expect("Expected at least one expression");
61 let (index, _msg) = if let Expr::Lit(ExprLit {
62 lit: Lit::Str(msg), ..
63 }) = msg_expr
64 {
65 let msg = msg.value();
66 let index = intern_string(&msg).expect("Failed to insert log string.");
67 (index, msg)
68 } else {
69 panic!("The first parameter of the argument needs to be a string literal.");
70 };
71 let level_str = match level {
72 CuLogLevel::Debug => quote! { Debug },
73 CuLogLevel::Info => quote! { Info },
74 CuLogLevel::Warning => quote! { Warning },
75 CuLogLevel::Error => quote! { Error },
76 CuLogLevel::Critical => quote! { Critical },
77 };
78 let prefix = quote! {
79 let mut log_entry = CuLogEntry::new(#index, CuLogLevel::#level_str);
80 };
81
82 let mut unnamed_params = vec![];
83 let mut named_params = vec![];
84
85 for expr in exprs_iter {
86 if let Expr::Assign(ExprAssign { left, right, .. }) = expr {
87 named_params.push((left, right));
88 } else {
89 unnamed_params.push(expr);
90 }
91 }
92
93 let unnamed_prints = unnamed_params.iter().map(|value| {
94 quote! {
95 let param = to_value(#value).expect("Failed to convert a parameter to a Value");
96 log_entry.add_param(ANONYMOUS, param);
97 }
98 });
99
100 let named_prints = named_params.iter().map(|(name, value)| {
101 let index = intern_string(quote!(#name).to_string().as_str())
102 .expect("Failed to insert log string.");
103 quote! {
104 let param = to_value(#value).expect("Failed to convert a parameter to a Value");
105 log_entry.add_param(#index, param);
106 }
107 });
108
109 #[cfg(not(debug_assertions))]
110 let log_stmt = quote! {
111 let r = log(&mut log_entry);
112 };
113
114 #[cfg(debug_assertions)]
115 let log_stmt = {
116 let keys: Vec<_> = named_params
117 .iter()
118 .map(|(name, _)| {
119 let name_str = quote!(#name).to_string(); let lit_str = syn::LitStr::new(&name_str, name.span()); quote!(#lit_str)
122 })
123 .collect();
124 quote! {
125 let r = log_debug_mode(&mut log_entry, #_msg, &[#(#keys),*]);
126 }
127 };
128
129 let postfix = quote! {
130 #log_stmt
131 if let Err(e) = r {
132 eprintln!("Warning: Failed to log: {}", e);
133 let backtrace = std::backtrace::Backtrace::capture();
134 eprintln!("{:?}", backtrace);
135 }
136 };
137
138 let expanded = quote! {
139 {
140 #prefix
141 #(#unnamed_prints)*
142 #(#named_prints)*
143 #postfix
144 }
145 };
146
147 expanded.into()
148}
149
150#[cfg(feature = "log-level-debug")]
173#[proc_macro]
174pub fn debug(input: TokenStream) -> TokenStream {
175 create_log_entry(input, CuLogLevel::Debug)
176}
177
178#[cfg(any(feature = "log-level-debug", feature = "log-level-info",))]
185#[proc_macro]
186pub fn info(input: TokenStream) -> TokenStream {
187 create_log_entry(input, CuLogLevel::Info)
188}
189
190#[cfg(any(
197 feature = "log-level-debug",
198 feature = "log-level-info",
199 feature = "log-level-warning",
200))]
201#[proc_macro]
202pub fn warning(input: TokenStream) -> TokenStream {
203 create_log_entry(input, CuLogLevel::Warning)
204}
205
206#[cfg(any(
213 feature = "log-level-debug",
214 feature = "log-level-info",
215 feature = "log-level-warning",
216 feature = "log-level-error",
217))]
218#[proc_macro]
219pub fn error(input: TokenStream) -> TokenStream {
220 create_log_entry(input, CuLogLevel::Error)
221}
222
223#[cfg(any(
230 feature = "log-level-debug",
231 feature = "log-level-info",
232 feature = "log-level-warning",
233 feature = "log-level-error",
234 feature = "log-level-critical",
235))]
236#[proc_macro]
237pub fn critical(input: TokenStream) -> TokenStream {
238 create_log_entry(input, CuLogLevel::Critical)
239}
240
241#[cfg(not(any(feature = "log-level-debug",)))]
243#[proc_macro]
244pub fn debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
245 reference_unused_variables(input)
246}
247
248#[cfg(not(any(feature = "log-level-debug", feature = "log-level-info",)))]
249#[proc_macro]
250pub fn info(input: TokenStream) -> TokenStream {
251 reference_unused_variables(input)
252}
253
254#[cfg(not(any(
255 feature = "log-level-debug",
256 feature = "log-level-info",
257 feature = "log-level-warning",
258)))]
259#[proc_macro]
260pub fn warning(input: TokenStream) -> TokenStream {
261 reference_unused_variables(input)
262}
263
264#[cfg(not(any(
265 feature = "log-level-debug",
266 feature = "log-level-info",
267 feature = "log-level-warning",
268 feature = "log-level-error",
269)))]
270#[proc_macro]
271pub fn error(input: TokenStream) -> TokenStream {
272 reference_unused_variables(input)
273}
274
275#[cfg(not(any(
276 feature = "log-level-debug",
277 feature = "log-level-info",
278 feature = "log-level-warning",
279 feature = "log-level-error",
280 feature = "log-level-critical",
281)))]
282#[proc_macro]
283pub fn critical(input: TokenStream) -> TokenStream {
284 reference_unused_variables(input)
285}
286
287#[proc_macro]
294pub fn intern(input: TokenStream) -> TokenStream {
295 let expr = syn::parse::<Expr>(input).expect("Failed to parse input as expression");
296 let (index, _msg) = if let Expr::Lit(ExprLit {
297 lit: Lit::Str(msg), ..
298 }) = expr
299 {
300 let msg = msg.value();
301 let index = intern_string(&msg).expect("Failed to insert log string.");
302 (index, msg)
303 } else {
304 panic!("The first parameter of the argument needs to be a string literal.");
305 };
306 quote! { #index }.into()
307}