-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support arbitrary ordering of optional arguments for logging macros
- Loading branch information
Showing
4 changed files
with
282 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.