Skip to content

Commit

Permalink
Support arbitrary ordering of optional arguments for logging macros
Browse files Browse the repository at this point in the history
  • Loading branch information
SpriteOvO committed Nov 26, 2024
1 parent c357f25 commit 804f06e
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 80 deletions.
39 changes: 34 additions & 5 deletions spdlog-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -33,9 +33,38 @@ pub fn runtime_pattern_disabled(_: TokenStream) -> TokenStream {
);
}

fn into_or_error(result: Result<TokenStream2>) -> 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<TokenStream2, Error>) -> TokenStream {
match result {
Ok(stream) => stream.into(),
Err(err) => panic!("{}", err),
Err(err) => err.emit(),
}
}
190 changes: 190 additions & 0 deletions spdlog-macros/src/normalize_forward.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
let callback = input.parse::<Ident>()?;
input.parse::<Token![=>]>()?;
let default_list = DefaultArgsList::parse(input)?;
input.parse::<Token![,]>()?;
let args = Args::parse(input)?;
Ok(Self {
callback,
default_list,
args,
})
}
}

struct DefaultArgsList(Vec<OptionalArg>);

impl Parse for DefaultArgsList {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![default]>()?;
let list;
bracketed!(list in input);
let list = list
.parse_terminated(OptionalArg::parse, Token![,])?
.into_iter()
.collect();
Ok(Self(list))
}
}

struct Args(Vec<Arg>);

impl Parse for Args {
fn parse(input: ParseStream) -> syn::Result<Self> {
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<Self> {
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<Self> {
let name = input.parse::<Ident>()?;
input.parse::<Token![:]>()?;
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<Self> {
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<Self> {
let content;
braced!(content in input);
let ts: TokenStream = content.parse()?;
Ok(Self(quote!({#ts})))
}
}

pub fn normalize(normalize: Normalize) -> syn::Result<TokenStream> {
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::<Vec<_>>();
let other_args = other_args
.into_iter()
.map(|arg| {
let ts = arg.into_token_stream();
quote!(#ts)
})
.collect::<Vec<_>>();

let emitted = quote! {
::spdlog::#callback!(#(#optional_args),*, #(#other_args),*)
};
Ok(emitted)
}
6 changes: 6 additions & 0 deletions spdlog/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down
Loading

0 comments on commit 804f06e

Please sign in to comment.