Skip to content

Commit c7f733a

Browse files
committed
Support arbitrary ordering of optional arguments for logging macros
1 parent eccadee commit c7f733a

File tree

7 files changed

+361
-80
lines changed

7 files changed

+361
-80
lines changed

spdlog-macros/src/lib.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55
//!
66
//! [`spdlog-rs`]: https://crates.io/crates/spdlog-rs
77
8+
mod normalize_forward;
89
mod pattern;
910

1011
use proc_macro::TokenStream;
1112
use proc_macro2::TokenStream as TokenStream2;
12-
use spdlog_internal::pattern_parser::Result;
13+
use quote::quote;
1314

1415
#[proc_macro]
1516
pub fn pattern(input: TokenStream) -> TokenStream {
1617
let pattern = syn::parse_macro_input!(input);
17-
into_or_error(pattern::pattern_impl(pattern))
18+
into_or_error(pattern::pattern_impl(pattern).map_err(Error::PatternParser))
1819
}
1920

2021
#[proc_macro]
@@ -23,7 +24,7 @@ pub fn runtime_pattern(input: TokenStream) -> TokenStream {
2324
// token which is used in the custom patterns.
2425

2526
let runtime_pattern = syn::parse_macro_input!(input);
26-
into_or_error(pattern::runtime_pattern_impl(runtime_pattern))
27+
into_or_error(pattern::runtime_pattern_impl(runtime_pattern).map_err(Error::PatternParser))
2728
}
2829

2930
#[proc_macro]
@@ -33,9 +34,39 @@ pub fn runtime_pattern_disabled(_: TokenStream) -> TokenStream {
3334
);
3435
}
3536

36-
fn into_or_error(result: Result<TokenStream2>) -> TokenStream {
37+
// Example:
38+
//
39+
// ```rust
40+
// normalize_forward!(callback => default[opt1: 1, opt2: {}, opt3: { 3 }, d], opt1: 10, a, b, c, opt3: { 30 });
41+
// // will be converted to
42+
// spdlog::callback!(opt1: 10, opt2: {}, opt3: { 30 }, d, a, b, c);
43+
// ```
44+
#[proc_macro]
45+
pub fn normalize_forward(input: TokenStream) -> TokenStream {
46+
let normalize = syn::parse_macro_input!(input);
47+
into_or_error(normalize_forward::normalize(normalize).map_err(Error::NormalizeForward))
48+
}
49+
50+
enum Error {
51+
PatternParser(spdlog_internal::pattern_parser::Error),
52+
NormalizeForward(syn::Error),
53+
}
54+
55+
impl Error {
56+
fn emit(self) -> TokenStream2 {
57+
match self {
58+
Error::PatternParser(err) => {
59+
let error = err.to_string();
60+
quote!(compile_error!(#error))
61+
}
62+
Error::NormalizeForward(err) => err.to_compile_error(),
63+
}
64+
}
65+
}
66+
67+
fn into_or_error(result: Result<TokenStream2, Error>) -> TokenStream {
3768
match result {
3869
Ok(stream) => stream.into(),
39-
Err(err) => panic!("{}", err),
70+
Err(err) => err.emit().into(),
4071
}
4172
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
use std::collections::HashSet;
2+
3+
use proc_macro2::TokenStream;
4+
use quote::{quote, ToTokens};
5+
use syn::{
6+
braced, bracketed,
7+
parse::{discouraged::Speculative, Parse, ParseStream},
8+
Expr, Ident, Token,
9+
};
10+
11+
pub struct Normalize {
12+
callback: Ident,
13+
default_list: DefaultArgsList,
14+
args: Args,
15+
}
16+
17+
impl Parse for Normalize {
18+
fn parse(input: ParseStream) -> syn::Result<Self> {
19+
let callback = input.parse::<Ident>()?;
20+
input.parse::<Token![=>]>()?;
21+
let default_list = DefaultArgsList::parse(input)?;
22+
input.parse::<Token![,]>()?;
23+
let args = Args::parse(input)?;
24+
Ok(Self {
25+
callback,
26+
default_list,
27+
args,
28+
})
29+
}
30+
}
31+
32+
struct DefaultArgsList(Vec<Arg>);
33+
34+
impl Parse for DefaultArgsList {
35+
fn parse(input: ParseStream) -> syn::Result<Self> {
36+
input.parse::<Token![default]>()?;
37+
let list;
38+
bracketed!(list in input);
39+
let list = list
40+
.parse_terminated(Arg::parse, Token![,])?
41+
.into_iter()
42+
.collect();
43+
Ok(Self(list))
44+
}
45+
}
46+
47+
struct Args(Vec<Arg>);
48+
49+
impl Parse for Args {
50+
fn parse(input: ParseStream) -> syn::Result<Self> {
51+
let args = input.parse_terminated(Arg::parse, Token![,])?;
52+
Ok(Self(args.into_iter().collect()))
53+
}
54+
}
55+
56+
enum Arg {
57+
Optional(OptionalArg),
58+
Other(ArgValue),
59+
}
60+
61+
impl Arg {
62+
fn as_optional(&self) -> Option<&OptionalArg> {
63+
match self {
64+
Self::Optional(arg) => Some(arg),
65+
_ => None,
66+
}
67+
}
68+
69+
fn as_optional_mut(&mut self) -> Option<&mut OptionalArg> {
70+
match self {
71+
Self::Optional(arg) => Some(arg),
72+
_ => None,
73+
}
74+
}
75+
}
76+
77+
impl Parse for Arg {
78+
fn parse(input: ParseStream) -> syn::Result<Self> {
79+
let fork = input.fork();
80+
match OptionalArg::parse(&fork) {
81+
Ok(opt_arg) => {
82+
input.advance_to(&fork);
83+
Ok(Self::Optional(opt_arg))
84+
}
85+
Err(_) => Ok(Self::Other(ArgValue::parse(input)?)),
86+
}
87+
}
88+
}
89+
90+
struct OptionalArg {
91+
name: Ident,
92+
value: ArgValue,
93+
}
94+
95+
impl Parse for OptionalArg {
96+
fn parse(input: ParseStream) -> syn::Result<Self> {
97+
let name = input.parse::<Ident>()?;
98+
input.parse::<Token![:]>()?;
99+
let value = ArgValue::parse(input)?;
100+
Ok(Self { name, value })
101+
}
102+
}
103+
104+
enum ArgValue {
105+
Expr(Expr),
106+
Braced(BraceAny),
107+
}
108+
109+
impl ArgValue {
110+
fn into_token_stream(self) -> TokenStream {
111+
match self {
112+
Self::Expr(expr) => expr.into_token_stream(),
113+
Self::Braced(braced) => braced.0,
114+
}
115+
}
116+
}
117+
118+
impl Parse for ArgValue {
119+
fn parse(input: ParseStream) -> syn::Result<Self> {
120+
let fork = input.fork();
121+
122+
match Expr::parse(&fork) {
123+
Ok(expr) => {
124+
input.advance_to(&fork);
125+
Ok(Self::Expr(expr))
126+
}
127+
Err(_) => Ok(BraceAny::parse(input).map(Self::Braced)?),
128+
}
129+
}
130+
}
131+
132+
struct BraceAny(TokenStream);
133+
134+
impl Parse for BraceAny {
135+
fn parse(input: ParseStream) -> syn::Result<Self> {
136+
let content;
137+
braced!(content in input);
138+
let ts: TokenStream = content.parse()?;
139+
Ok(Self(quote!({#ts})))
140+
}
141+
}
142+
143+
fn check_inputs(normalize: &Normalize) -> syn::Result<()> {
144+
let mut seen_keys = HashSet::new();
145+
for arg in normalize.args.0.iter().filter_map(|arg| arg.as_optional()) {
146+
if !seen_keys.insert(&arg.name) {
147+
return Err(syn::Error::new(
148+
arg.name.span(),
149+
format!("found duplicate optional argument '{}'", arg.name),
150+
));
151+
}
152+
}
153+
154+
let groups = normalize
155+
.args
156+
.0
157+
.split_inclusive(|arg| matches!(arg, Arg::Optional(_)))
158+
.filter(|group| {
159+
group
160+
.iter()
161+
.find(|arg| !matches!(arg, Arg::Optional(_)))
162+
.is_some()
163+
})
164+
.collect::<Vec<_>>();
165+
if groups.len() > 1 {
166+
return Err(syn::Error::new(
167+
groups
168+
.first()
169+
.and_then(|group| group.last())
170+
.and_then(|arg| arg.as_optional())
171+
.unwrap()
172+
.name
173+
.span(),
174+
"optional arguments cannot occur in the middle of regular arguments",
175+
));
176+
}
177+
178+
Ok(())
179+
}
180+
181+
pub fn normalize(normalize: Normalize) -> syn::Result<TokenStream> {
182+
check_inputs(&normalize)?;
183+
184+
let mut default_args = normalize.default_list.0;
185+
let mut other_args = vec![];
186+
187+
for input_arg in normalize.args.0 {
188+
match input_arg {
189+
Arg::Optional(input_arg) => {
190+
let stored = default_args
191+
.iter_mut()
192+
.find_map(|allowed| {
193+
allowed
194+
.as_optional_mut()
195+
.and_then(|allowed| (allowed.name == input_arg.name).then_some(allowed))
196+
})
197+
.ok_or_else(|| {
198+
syn::Error::new(
199+
input_arg.name.span(),
200+
format!("unknown optional parameter '{}'", input_arg.name),
201+
)
202+
})?;
203+
stored.value = input_arg.value;
204+
}
205+
Arg::Other(input_arg) => {
206+
other_args.push(input_arg);
207+
}
208+
}
209+
}
210+
211+
let callback = normalize.callback;
212+
let default_args = default_args
213+
.into_iter()
214+
.map(|arg| match arg {
215+
Arg::Optional(arg) => {
216+
let name = arg.name;
217+
let value = arg.value.into_token_stream();
218+
quote!(#name: #value)
219+
}
220+
Arg::Other(arg) => {
221+
let value = arg.into_token_stream();
222+
quote!(#value)
223+
}
224+
})
225+
.collect::<Vec<_>>();
226+
let other_args = other_args
227+
.into_iter()
228+
.map(|arg| {
229+
let ts = arg.into_token_stream();
230+
quote!(#ts)
231+
})
232+
.collect::<Vec<_>>();
233+
234+
let emitted = quote! {
235+
::spdlog::#callback!(#(#default_args),*, #(#other_args),*)
236+
};
237+
Ok(emitted)
238+
}

spdlog/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@
278278
#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
279279
#![warn(missing_docs)]
280280

281+
// Used for referencing from proc-macros
282+
// Credits: https://stackoverflow.com/a/57049687
283+
extern crate self as spdlog;
284+
281285
mod env_level;
282286
pub mod error;
283287
pub mod formatter;
@@ -308,6 +312,8 @@ pub use log_crate_proxy::*;
308312
pub use logger::*;
309313
pub use record::*;
310314
pub use source_location::*;
315+
#[doc(hidden)]
316+
pub use spdlog_macros::normalize_forward as __normalize_forward;
311317
pub use string_buf::StringBuf;
312318
#[cfg(feature = "multi-thread")]
313319
pub use thread_pool::*;

0 commit comments

Comments
 (0)