extern crate proc_macro;
use cu29_intern_strs::intern_string;
use cu29_log::CuLogLevel;
use proc_macro::TokenStream;
#[cfg(feature = "textlogs")]
use proc_macro_crate::{FoundCrate, crate_name};
#[cfg(feature = "textlogs")]
use proc_macro2::Span;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
#[allow(unused)]
use syn::Token;
use syn::parse::Parser;
#[cfg(any(feature = "textlogs", debug_assertions))]
use syn::spanned::Spanned;
use syn::{Expr, ExprLit, Lit};
#[allow(unused)]
fn reference_unused_variables(input: TokenStream) -> TokenStream {
let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
if let Ok(exprs) = parser.parse(input.clone()) {
let mut var_usages = Vec::new();
for expr in exprs.iter().skip(1) {
match expr {
syn::Expr::Assign(assign_expr) => {
let value_expr = &assign_expr.right;
var_usages.push(quote::quote! { let _ = &#value_expr; });
}
_ => {
var_usages.push(quote::quote! { let _ = &#expr; });
}
}
}
return quote::quote! { { #(#var_usages;)* } }.into();
}
TokenStream::new()
}
#[allow(unused)]
fn create_log_entry(input: TokenStream, level: CuLogLevel) -> TokenStream {
use quote::quote;
use syn::{Expr, ExprAssign, ExprLit, Lit, Token};
let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated;
let exprs = match parser.parse(input) {
Ok(exprs) => exprs,
Err(err) => return err.to_compile_error().into(),
};
let mut exprs_iter = exprs.iter();
#[cfg(not(feature = "std"))]
const STD: bool = false;
#[cfg(feature = "std")]
const STD: bool = true;
let msg_expr = match exprs_iter.next() {
Some(expr) => expr,
None => {
return syn::Error::new(
proc_macro2::Span::call_site(),
"Expected at least one expression",
)
.to_compile_error()
.into();
}
};
let (index, msg_str) = if let Expr::Lit(ExprLit {
lit: Lit::Str(msg), ..
}) = msg_expr
{
let s = msg.value();
let index = match intern_string(&s) {
Some(index) => index,
None => {
return syn::Error::new_spanned(msg_expr, "Failed to intern log string.")
.to_compile_error()
.into();
}
};
(index, s)
} else {
return syn::Error::new_spanned(
msg_expr,
"The first parameter of the argument needs to be a string literal.",
)
.to_compile_error()
.into();
};
let level_ident = match level {
CuLogLevel::Debug => quote! { Debug },
CuLogLevel::Info => quote! { Info },
CuLogLevel::Warning => quote! { Warning },
CuLogLevel::Error => quote! { Error },
CuLogLevel::Critical => quote! { Critical },
};
let mut unnamed_params = Vec::<&Expr>::new();
let mut named_params = Vec::<(&Expr, &Expr)>::new();
for expr in exprs_iter {
if let Expr::Assign(ExprAssign { left, right, .. }) = expr {
named_params.push((left, right));
} else {
unnamed_params.push(expr);
}
}
let unnamed_prints = unnamed_params.iter().map(|value| {
quote! {
let param = to_value(#value).expect("Failed to convert a parameter to a Value");
log_entry.add_param(ANONYMOUS, param);
}
});
let mut named_prints = Vec::with_capacity(named_params.len());
for (name, value) in &named_params {
let name_str = quote!(#name).to_string();
let idx = match intern_string(&name_str) {
Some(idx) => idx,
None => {
return syn::Error::new_spanned(name, "Failed to intern log parameter name.")
.to_compile_error()
.into();
}
};
named_prints.push(quote! {
let param = to_value(#value).expect("Failed to convert a parameter to a Value");
log_entry.add_param(#idx, param);
});
}
#[cfg(feature = "textlogs")]
let (defmt_fmt_lit, defmt_args_unnamed_ts, defmt_args_named_ts, defmt_available) = {
let defmt_fmt_lit = {
let mut s = msg_str.clone();
if !named_params.is_empty() {
s.push_str(" |");
}
for (name, _) in named_params.iter() {
let name_str = quote!(#name).to_string();
s.push(' ');
s.push_str(&name_str);
s.push_str("={:?}");
}
syn::LitStr::new(&s, msg_expr.span())
};
let defmt_args_unnamed_ts: Vec<TokenStream2> =
unnamed_params.iter().map(|e| quote! { #e }).collect();
let defmt_args_named_ts: Vec<TokenStream2> = named_params
.iter()
.map(|(_, rhs)| quote! { #rhs })
.collect();
let defmt_available = crate_name("defmt").is_ok();
(
defmt_fmt_lit,
defmt_args_unnamed_ts,
defmt_args_named_ts,
defmt_available,
)
};
#[cfg(feature = "textlogs")]
fn defmt_macro_path(level: CuLogLevel) -> TokenStream2 {
let macro_ident = match level {
CuLogLevel::Debug => quote! { defmt_debug },
CuLogLevel::Info => quote! { defmt_info },
CuLogLevel::Warning => quote! { defmt_warn },
CuLogLevel::Error => quote! { defmt_error },
CuLogLevel::Critical => quote! { defmt_error },
};
let (base, use_prelude) = match crate_name("cu29-log") {
Ok(FoundCrate::Name(name)) => {
let ident = proc_macro2::Ident::new(&name, Span::call_site());
(quote! { ::#ident }, false)
}
Ok(FoundCrate::Itself) => (quote! { crate }, false),
Err(_) => match crate_name("cu29") {
Ok(FoundCrate::Name(name)) => {
let ident = proc_macro2::Ident::new(&name, Span::call_site());
(quote! { ::#ident }, true)
}
Ok(FoundCrate::Itself) => (quote! { crate }, true),
Err(_) => (quote! { ::cu29_log }, false),
},
};
if use_prelude {
quote! { #base::prelude::#macro_ident }
} else {
quote! { #base::#macro_ident }
}
}
#[cfg(not(debug_assertions))]
let log_stmt = quote! { let r = log(&mut log_entry); };
#[cfg(debug_assertions)]
let log_stmt: TokenStream2 = {
let keys: Vec<_> = named_params
.iter()
.map(|(name, _)| {
let name_str = quote!(#name).to_string();
syn::LitStr::new(&name_str, name.span())
})
.collect();
quote! {
let r = log_debug_mode(&mut log_entry, #msg_str, &[#(#keys),*]);
}
};
let error_handling: Option<TokenStream2> = Some(quote! {
if let Err(_e) = r {
let _ = &_e;
}
});
#[cfg(feature = "textlogs")]
let defmt_macro: TokenStream2 = defmt_macro_path(level);
#[cfg(feature = "textlogs")]
let maybe_inject_defmt: Option<TokenStream2> = if STD || !defmt_available {
None } else {
Some(quote! {
#defmt_macro!(#defmt_fmt_lit, #(#defmt_args_unnamed_ts,)* #(#defmt_args_named_ts,)*);
})
};
#[cfg(not(feature = "textlogs"))]
let maybe_inject_defmt: Option<TokenStream2> = None;
quote! {{
let mut log_entry = CuLogEntry::new(#index, CuLogLevel::#level_ident);
#(#unnamed_prints)*
#(#named_prints)*
#maybe_inject_defmt
#log_stmt
#error_handling
}}
.into()
}
#[cfg(any(feature = "log-level-debug", cu29_default_log_level_debug))]
#[proc_macro]
pub fn debug(input: TokenStream) -> TokenStream {
create_log_entry(input, CuLogLevel::Debug)
}
#[cfg(any(feature = "log-level-debug", feature = "log-level-info",))]
#[proc_macro]
pub fn info(input: TokenStream) -> TokenStream {
create_log_entry(input, CuLogLevel::Info)
}
#[cfg(any(
feature = "log-level-debug",
feature = "log-level-info",
feature = "log-level-warning",
))]
#[proc_macro]
pub fn warning(input: TokenStream) -> TokenStream {
create_log_entry(input, CuLogLevel::Warning)
}
#[cfg(any(
feature = "log-level-debug",
feature = "log-level-info",
feature = "log-level-warning",
feature = "log-level-error",
))]
#[proc_macro]
pub fn error(input: TokenStream) -> TokenStream {
create_log_entry(input, CuLogLevel::Error)
}
#[cfg(any(
feature = "log-level-debug",
feature = "log-level-info",
feature = "log-level-warning",
feature = "log-level-error",
feature = "log-level-critical",
))]
#[proc_macro]
pub fn critical(input: TokenStream) -> TokenStream {
create_log_entry(input, CuLogLevel::Critical)
}
#[cfg(not(any(feature = "log-level-debug", cu29_default_log_level_debug)))]
#[proc_macro]
pub fn debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
reference_unused_variables(input)
}
#[cfg(not(any(feature = "log-level-debug", feature = "log-level-info",)))]
#[proc_macro]
pub fn info(input: TokenStream) -> TokenStream {
reference_unused_variables(input)
}
#[cfg(not(any(
feature = "log-level-debug",
feature = "log-level-info",
feature = "log-level-warning",
)))]
#[proc_macro]
pub fn warning(input: TokenStream) -> TokenStream {
reference_unused_variables(input)
}
#[cfg(not(any(
feature = "log-level-debug",
feature = "log-level-info",
feature = "log-level-warning",
feature = "log-level-error",
)))]
#[proc_macro]
pub fn error(input: TokenStream) -> TokenStream {
reference_unused_variables(input)
}
#[cfg(not(any(
feature = "log-level-debug",
feature = "log-level-info",
feature = "log-level-warning",
feature = "log-level-error",
feature = "log-level-critical",
)))]
#[proc_macro]
pub fn critical(input: TokenStream) -> TokenStream {
reference_unused_variables(input)
}
#[proc_macro]
pub fn intern(input: TokenStream) -> TokenStream {
let expr = match syn::parse::<Expr>(input) {
Ok(expr) => expr,
Err(err) => return err.to_compile_error().into(),
};
let index = if let Expr::Lit(ExprLit {
lit: Lit::Str(msg), ..
}) = &expr
{
let msg = msg.value();
match intern_string(&msg) {
Some(index) => index,
None => {
return syn::Error::new_spanned(&expr, "Failed to intern log string.")
.to_compile_error()
.into();
}
}
} else {
return syn::Error::new_spanned(
&expr,
"The first parameter of the argument needs to be a string literal.",
)
.to_compile_error()
.into();
};
quote! { #index }.into()
}