diff --git a/spdlog-macros/src/lib.rs b/spdlog-macros/src/lib.rs index c9d60fd..53149d6 100644 --- a/spdlog-macros/src/lib.rs +++ b/spdlog-macros/src/lib.rs @@ -5,16 +5,16 @@ //! //! [`spdlog-rs`]: https://crates.io/crates/spdlog-rs +mod normalize_forward; mod pattern; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use spdlog_internal::pattern_parser::Result; #[proc_macro] pub fn pattern(input: TokenStream) -> TokenStream { let pattern = syn::parse_macro_input!(input); - into_or_error(pattern::pattern_impl(pattern)) + into_or_error(pattern::pattern_impl(pattern).map_err(Error::PatternParser)) } #[proc_macro] @@ -23,7 +23,7 @@ pub fn runtime_pattern(input: TokenStream) -> TokenStream { // token which is used in the custom patterns. let runtime_pattern = syn::parse_macro_input!(input); - into_or_error(pattern::runtime_pattern_impl(runtime_pattern)) + into_or_error(pattern::runtime_pattern_impl(runtime_pattern).map_err(Error::PatternParser)) } #[proc_macro] @@ -33,9 +33,38 @@ pub fn runtime_pattern_disabled(_: TokenStream) -> TokenStream { ); } -fn into_or_error(result: Result) -> TokenStream { +// This macro performs the following things: +// - provide default values for all unspecified optional parameters; +// - place all optional parameters before other parameters; +// - sort optional parameters by alphabetical order; +// - keep the other parameters in their original order and place them after all +// optional parameters. +#[proc_macro] +pub fn normalize_forward(input: TokenStream) -> TokenStream { + let normalize = syn::parse_macro_input!(input); + into_or_error(normalize_forward::normalize(normalize).map_err(Error::NormalizeForward)) +} + +enum Error { + PatternParser(spdlog_internal::pattern_parser::Error), + NormalizeForward(syn::Error), +} + +impl Error { + fn emit(self) -> ! { + match self { + Error::PatternParser(err) => panic!("{}", err), + Error::NormalizeForward(err) => { + // FIXME: Use `err.to_compile_error()` + panic!("{}", err) + } + } + } +} + +fn into_or_error(result: Result) -> TokenStream { match result { Ok(stream) => stream.into(), - Err(err) => panic!("{}", err), + Err(err) => err.emit(), } } diff --git a/spdlog-macros/src/normalize_forward.rs b/spdlog-macros/src/normalize_forward.rs new file mode 100644 index 0000000..b1ae55b --- /dev/null +++ b/spdlog-macros/src/normalize_forward.rs @@ -0,0 +1,190 @@ +use proc_macro2::{Delimiter, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + braced, bracketed, + parse::{ + discouraged::{AnyDelimiter, Speculative}, + Parse, ParseStream, + }, + token, Expr, Ident, Token, +}; + +pub struct Normalize { + callback: Ident, + default_list: DefaultArgsList, + args: Args, +} + +impl Parse for Normalize { + fn parse(input: ParseStream) -> syn::Result { + let callback = input.parse::()?; + input.parse::]>()?; + let default_list = DefaultArgsList::parse(input)?; + input.parse::()?; + let args = Args::parse(input)?; + Ok(Self { + callback, + default_list, + args, + }) + } +} + +struct DefaultArgsList(Vec); + +impl Parse for DefaultArgsList { + fn parse(input: ParseStream) -> syn::Result { + input.parse::()?; + let list; + bracketed!(list in input); + let list = list + .parse_terminated(OptionalArg::parse, Token![,])? + .into_iter() + .collect(); + Ok(Self(list)) + } +} + +struct Args(Vec); + +impl Parse for Args { + fn parse(input: ParseStream) -> syn::Result { + let args = input.parse_terminated(Arg::parse, Token![,])?; + Ok(Self(args.into_iter().collect())) + } +} + +#[derive(Debug)] +enum Arg { + Optional(OptionalArg), + Other(ArgValue), +} + +impl Arg { + fn as_optional(&self) -> Option<&OptionalArg> { + match self { + Self::Optional(arg) => Some(arg), + _ => None, + } + } +} + +impl Parse for Arg { + fn parse(input: ParseStream) -> syn::Result { + let fork = input.fork(); + match OptionalArg::parse(&fork) { + Ok(opt_arg) => { + input.advance_to(&fork); + Ok(Self::Optional(opt_arg)) + } + Err(_) => Ok(Self::Other(ArgValue::parse(input)?)), + } + } +} + +#[derive(Debug)] +struct OptionalArg { + name: Ident, + value: ArgValue, +} + +impl Parse for OptionalArg { + fn parse(input: ParseStream) -> syn::Result { + let name = input.parse::()?; + input.parse::()?; + let value = ArgValue::parse(&input)?; + Ok(Self { name, value }) + } +} + +#[derive(Debug)] +enum ArgValue { + Expr(Expr), + Braced(BraceAny), +} + +impl ArgValue { + fn into_token_stream(self) -> TokenStream { + match self { + Self::Expr(expr) => expr.into_token_stream(), + Self::Braced(braced) => braced.0, + } + } +} + +impl Parse for ArgValue { + fn parse(input: ParseStream) -> syn::Result { + let fork = input.fork(); + + match Expr::parse(&fork) { + Ok(expr) => { + input.advance_to(&fork); + Ok(Self::Expr(expr)) + } + Err(_) => Ok(BraceAny::parse(input).map(Self::Braced)?), + } + } +} + +#[derive(Debug)] +struct BraceAny(TokenStream); + +impl Parse for BraceAny { + fn parse(input: ParseStream) -> syn::Result { + let content; + braced!(content in input); + let ts: TokenStream = content.parse()?; + Ok(Self(quote!({#ts}))) + } +} + +pub fn normalize(normalize: Normalize) -> syn::Result { + let mut optional_args = normalize.default_list.0; + let mut other_args = vec![]; + + // TODO: + // - Check duplicate optional arguments. + // - Check optional arguments not in the middle. + + for input_arg in normalize.args.0 { + match input_arg { + Arg::Optional(input_arg) => { + let stored = optional_args + .iter_mut() + .find(|allowed| allowed.name == input_arg.name) + .ok_or_else(|| { + syn::Error::new( + input_arg.name.span(), + format!("invalid optional parameter '{}'", input_arg.name), + ) + })?; + stored.value = input_arg.value; + } + Arg::Other(input_arg) => { + other_args.push(input_arg); + } + } + } + + let callback = normalize.callback; + let optional_args = optional_args + .into_iter() + .map(|arg| { + let name = arg.name; + let value = arg.value.into_token_stream(); + quote!(#name: #value) + }) + .collect::>(); + let other_args = other_args + .into_iter() + .map(|arg| { + let ts = arg.into_token_stream(); + quote!(#ts) + }) + .collect::>(); + + let emitted = quote! { + ::spdlog::#callback!(#(#optional_args),*, #(#other_args),*) + }; + Ok(emitted) +} diff --git a/spdlog/src/lib.rs b/spdlog/src/lib.rs index ed5d420..ebac34c 100644 --- a/spdlog/src/lib.rs +++ b/spdlog/src/lib.rs @@ -278,6 +278,10 @@ #![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))] #![warn(missing_docs)] +// Used for referencing from proc-macros +// Credits: https://stackoverflow.com/a/57049687 +extern crate self as spdlog; + mod env_level; pub mod error; pub mod formatter; @@ -308,6 +312,8 @@ pub use log_crate_proxy::*; pub use logger::*; pub use record::*; pub use source_location::*; +#[doc(hidden)] +pub use spdlog_macros::normalize_forward as __normalize_forward; pub use string_buf::StringBuf; #[cfg(feature = "multi-thread")] pub use thread_pool::*; diff --git a/spdlog/src/log_macros.rs b/spdlog/src/log_macros.rs index 05f92e8..4647443 100644 --- a/spdlog/src/log_macros.rs +++ b/spdlog/src/log_macros.rs @@ -21,6 +21,14 @@ /// [`Level`]: crate::Level #[macro_export] macro_rules! log { + ($($input:tt)+) => { + $crate::__normalize_forward!(__log_impl => default[logger: $crate::default_logger(), kv: {}], $($input)+) + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __log_impl { (logger: $logger:expr, kv: $kv:tt, $level:expr, $($arg:tt)+) => ({ let logger = &$logger; const LEVEL: $crate::Level = $level; @@ -29,9 +37,6 @@ macro_rules! log { $crate::__log(logger, LEVEL, $crate::source_location_current!(), $crate::__kv!($kv), format_args!($($arg)+)); } }); - (logger: $logger:expr, $level:expr, $($arg:tt)+) => ($crate::log!(logger: $logger, kv: {}, $level, $($arg)+)); - (kv: $kv:tt, $level:expr, $($arg:tt)+) => ($crate::log!(logger: $crate::default_logger(), kv: $kv, $level, $($arg)+)); - ($level:expr, $($arg:tt)+) => ($crate::log!(logger: $crate::default_logger(), kv: {}, $level, $($arg)+)); } /// Logs a message at the critical level. @@ -52,18 +57,9 @@ macro_rules! log { /// ``` #[macro_export] macro_rules! critical { - (logger: $logger:expr, kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(logger: $logger, kv: $kv, $crate::Level::Critical, $($arg)+) - ); - (logger: $logger:expr, $($arg:tt)+) => ( - $crate::log!(logger: $logger, $crate::Level::Critical, $($arg)+) - ); - (kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(kv: $kv, $crate::Level::Critical, $($arg)+) - ); - ($($arg:tt)+) => ( - $crate::log!($crate::Level::Critical, $($arg)+) - ) + ($($input:tt)+) => { + $crate::__normalize_forward!(__log_impl => default[logger: $crate::default_logger(), kv: {}], $crate::Level::Critical, $($input)+) + }; } /// Logs a message at the error level. @@ -84,18 +80,9 @@ macro_rules! critical { /// ``` #[macro_export] macro_rules! error { - (logger: $logger:expr, kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(logger: $logger, kv: $kv, $crate::Level::Error, $($arg)+) - ); - (logger: $logger:expr, $($arg:tt)+) => ( - $crate::log!(logger: $logger, $crate::Level::Error, $($arg)+) - ); - (kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(kv: $kv, $crate::Level::Error, $($arg)+) - ); - ($($arg:tt)+) => ( - $crate::log!($crate::Level::Error, $($arg)+) - ) + ($($input:tt)+) => { + $crate::__normalize_forward!(__log_impl => default[logger: $crate::default_logger(), kv: {}], $crate::Level::Error, $($input)+) + }; } /// Logs a message at the warn level. @@ -116,18 +103,9 @@ macro_rules! error { /// ``` #[macro_export] macro_rules! warn { - (logger: $logger:expr, kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(logger: $logger, kv: $kv, $crate::Level::Warn, $($arg)+) - ); - (logger: $logger:expr, $($arg:tt)+) => ( - $crate::log!(logger: $logger, $crate::Level::Warn, $($arg)+) - ); - (kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(kv: $kv, $crate::Level::Warn, $($arg)+) - ); - ($($arg:tt)+) => ( - $crate::log!($crate::Level::Warn, $($arg)+) - ) + ($($input:tt)+) => { + $crate::__normalize_forward!(__log_impl => default[logger: $crate::default_logger(), kv: {}], $crate::Level::Warn, $($input)+) + }; } /// Logs a message at the info level. @@ -149,18 +127,9 @@ macro_rules! warn { /// ``` #[macro_export] macro_rules! info { - (logger: $logger:expr, kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(logger: $logger, kv: $kv, $crate::Level::Info, $($arg)+) - ); - (logger: $logger:expr, $($arg:tt)+) => ( - $crate::log!(logger: $logger, $crate::Level::Info, $($arg)+) - ); - (kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(kv: $kv, $crate::Level::Info, $($arg)+) - ); - ($($arg:tt)+) => ( - $crate::log!($crate::Level::Info, $($arg)+) - ) + ($($input:tt)+) => { + $crate::__normalize_forward!(__log_impl => default[logger: $crate::default_logger(), kv: {}], $crate::Level::Info, $($input)+) + }; } /// Logs a message at the debug level. @@ -182,18 +151,9 @@ macro_rules! info { /// ``` #[macro_export] macro_rules! debug { - (logger: $logger:expr, kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(logger: $logger, kv: $kv, $crate::Level::Debug, $($arg)+) - ); - (logger: $logger:expr, $($arg:tt)+) => ( - $crate::log!(logger: $logger, $crate::Level::Debug, $($arg)+) - ); - (kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(kv: $kv, $crate::Level::Debug, $($arg)+) - ); - ($($arg:tt)+) => ( - $crate::log!($crate::Level::Debug, $($arg)+) - ) + ($($input:tt)+) => { + $crate::__normalize_forward!(__log_impl => default[logger: $crate::default_logger(), kv: {}], $crate::Level::Debug, $($input)+) + }; } /// Logs a message at the trace level. @@ -217,18 +177,9 @@ macro_rules! debug { /// ``` #[macro_export] macro_rules! trace { - (logger: $logger:expr, kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(logger: $logger, kv: $kv, $crate::Level::Trace, $($arg)+) - ); - (logger: $logger:expr, $($arg:tt)+) => ( - $crate::log!(logger: $logger, $crate::Level::Trace, $($arg)+) - ); - (kv: $kv:tt, $($arg:tt)+) => ( - $crate::log!(kv: $kv, $crate::Level::Trace, $($arg)+) - ); - ($($arg:tt)+) => ( - $crate::log!($crate::Level::Trace, $($arg)+) - ) + ($($input:tt)+) => { + $crate::__normalize_forward!(__log_impl => default[logger: $crate::default_logger(), kv: {}], $crate::Level::Trace, $($input)+) + }; } #[doc(hidden)] @@ -320,6 +271,16 @@ mod tests { Level::Info, "logger, kv(s,mod,vref)".to_string(), ), + ( + vec![(KeyInner::StaticStr("mod_di"), "display".to_string())], + Level::Debug, + "arbitrary order = logger, fmt, kv".to_string(), + ), + ( + vec![(KeyInner::StaticStr("mod_di"), "display".to_string())], + Level::Debug, + "arbitrary order = fmt, logger, kv".to_string(), + ), ]; log!(logger: test, Level::Info, "logger"); @@ -331,6 +292,8 @@ mod tests { log!(logger: test, kv: { n }, Level::Trace, "logger, kv(1,vref)"); log!(logger: test, kv: { mod_di: }, Level::Debug, "logger, kv(mod,vref)"); log!(logger: test, kv: { n, mod_di:, mod_de:? }, Level::Info, "logger, kv(s,mod,vref)"); + log!(logger: test, Level::Debug, "arbitrary order = logger, fmt, kv", kv: { mod_di: }); + log!(Level::Debug, "arbitrary order = fmt, logger, kv", logger: test, kv: { mod_di: }); macro_rules! add_records { ( $($level:ident => $variant:ident),+ ) => { @@ -383,6 +346,20 @@ mod tests { Level::$variant, format!("{}: logger, kv(s,mod,vref)", stringify!($level)) )); + + $level!(logger: test, "{}: arbitrary order = logger, fmt, kv", stringify!($level), kv: { mod_di: }); + check.push(( + vec![(KeyInner::StaticStr("mod_di"), "display".to_string())], + Level::$variant, + format!("{}: arbitrary order = logger, fmt, kv", stringify!($level)) + )); + + $level!("{}: arbitrary order = fmt, logger, kv", stringify!($level), logger: test, kv: { mod_di: }); + check.push(( + vec![(KeyInner::StaticStr("mod_di"), "display".to_string())], + Level::$variant, + format!("{}: arbitrary order = fmt, logger, kv", stringify!($level)) + )); )+ }; }