Skip to content

Commit

Permalink
Add runtime_pattern macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Lancern committed Dec 31, 2023
1 parent fd2c3cf commit bd2c4f9
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 226 deletions.
2 changes: 1 addition & 1 deletion spdlog-internal/src/pattern_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub mod parse;
mod registry;

pub use error::{Error, Result};
pub use registry::PatternRegistry;
pub use registry::{check_custom_pattern_names, PatternRegistry};

#[derive(
Clone, Copy, Debug, Eq, PartialEq, IntoStaticStr, EnumDiscriminants, EnumIter, EnumString,
Expand Down
86 changes: 84 additions & 2 deletions spdlog-internal/src/pattern_parser/registry.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{
borrow::Cow,
collections::{hash_map::Entry, HashMap},
fmt::Debug,
collections::{hash_map::Entry, HashMap, HashSet},
fmt::{Debug, Display, Formatter},
};

use super::{error::TemplateError, BuiltInFormatter, Error, PatternKind, Result};
Expand Down Expand Up @@ -89,3 +89,85 @@ impl<F> PatternRegistry<F> {
}
}
}

pub fn check_custom_pattern_names<N, I>(names: I) -> std::result::Result<(), InvalidCustomNames>
where
I: IntoIterator<Item = N>,
N: AsRef<str>,
{
let builtin_names: HashSet<&'static str> =
BuiltInFormatter::iter().map(|f| f.placeholder()).collect();
let mut seen_names: HashMap<String, u32> = HashMap::new();
let mut invalid_names = Vec::new();

for n in names {
let n = n.as_ref();

if builtin_names.contains(&n) {
invalid_names.push(InvalidCustomName::ConflictWithBuiltin(String::from(n)));
}

if seen_names.contains_key(n) {
let seen_names_count = seen_names.get_mut(n).unwrap();
*seen_names_count += 1;
if *seen_names_count == 2 {
invalid_names.push(InvalidCustomName::ConflictWithOther(String::from(n)));
}
} else {
seen_names.insert(String::from(n), 1);
}
}

if !invalid_names.is_empty() {
return Err(InvalidCustomNames(invalid_names));
}

Ok(())
}

#[derive(Debug)]
pub struct InvalidCustomNames(Vec<InvalidCustomName>);

impl Display for InvalidCustomNames {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{} errors detected:", self.0.len())?;
for i in &self.0 {
writeln!(f, "{}", i)?;
}
Ok(())
}
}

impl std::error::Error for InvalidCustomNames {}

impl IntoIterator for InvalidCustomNames {
type Item = InvalidCustomName;
type IntoIter = <Vec<InvalidCustomName> as IntoIterator>::IntoIter;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

#[derive(Debug)]
pub enum InvalidCustomName {
ConflictWithBuiltin(String),
ConflictWithOther(String),
}

impl Display for InvalidCustomName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConflictWithBuiltin(name) => write!(
f,
"custom pattern name \"{}\" conflicts with a built-in pattern",
name
),
Self::ConflictWithOther(name) => write!(
f,
"custom pattern name \"{}\" is bound multiple times",
name
),
}
}
}
9 changes: 9 additions & 0 deletions spdlog-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ pub fn pattern(input: TokenStream) -> TokenStream {
Err(err) => panic!("{}", err),
}
}

#[proc_macro]
pub fn runtime_pattern(input: TokenStream) -> TokenStream {
// We must make this macro a procedural macro because macro by example
// cannot match the "$" token which is used in the custom patterns.

let runtime_pattern = syn::parse_macro_input!(input);
pattern::runtime_pattern_impl(runtime_pattern).into()
}
77 changes: 74 additions & 3 deletions spdlog-macros/src/pattern/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
mod synthesis;

use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use self_cell::self_cell;
use spdlog_internal::pattern_parser::{parse::Template, PatternRegistry, Result};
use syn::{
braced,
parse::{Parse, ParseStream},
Ident, LitStr, Path, Token,
Expr, Ident, LitStr, Path, Token,
};
use synthesis::Synthesiser;

Expand All @@ -19,6 +20,53 @@ pub fn pattern_impl(pattern: Pattern) -> Result<TokenStream> {
Synthesiser::new(registry).synthesize(pattern.template())
}

pub fn runtime_pattern_impl(runtime_pattern: RuntimePattern) -> TokenStream {
let custom_pattern_names: Vec<_> = runtime_pattern
.custom_patterns
.0
.iter()
.map(|(name, _)| name.to_string())
.collect();
if let Err(err) =
spdlog_internal::pattern_parser::check_custom_pattern_names(custom_pattern_names)
{
panic!("invalid custom pattern names: {}", err);
}

let custom_pattern_register_calls: Vec<_> = runtime_pattern
.custom_patterns
.0
.into_iter()
.map(|(name, factory)| {
let name_literal = LitStr::new(&name.to_string(), Span::mixed_site());
quote! {
registry.register_custom(#name_literal, Box::new(|| Box::new(#factory())))
.expect("unexpected panic, please report a bug to spdlog-rs");
}
})
.collect();

let template = runtime_pattern.template;
quote! {
{
let template = #template;
let pattern_registry = {
let mut registry = spdlog_internal
::pattern_parser
::PatternRegistry
::<Box<dyn Fn() -> Box<dyn spdlog::formatter::Pattern>>>
::with_builtin();
#(#custom_pattern_register_calls)*
registry
};
spdlog::formatter::RuntimePattern::new_with_custom_patterns(
template,
pattern_registry,
)
}
}
}

/// A parsed pattern.
///
/// A [`Pattern`] gives a structural representation of a pattern parsed from the
Expand Down Expand Up @@ -72,7 +120,30 @@ impl Parse for Pattern {
}
}

/////
/// A parsed runtime pattern.
///
/// The only difference between a pattern and a runtime pattern is that the
/// template string of a pattern must be a string literal, while the template
/// string of a runtime pattern can be a runtime expression that evaluates to a
/// string.
pub struct RuntimePattern {
template: Expr,
custom_patterns: CustomPatterns,
}

impl Parse for RuntimePattern {
fn parse(input: ParseStream) -> syn::Result<Self> {
let template_expr = input.parse::<Expr>()?;
input.parse::<Option<Token![,]>>()?;
let custom_patterns = input.parse()?;

let ret = RuntimePattern {
template: template_expr,
custom_patterns,
};
Ok(ret)
}
}

/// Mapping from user-provided patterns to formatters.
struct CustomPatterns(Vec<(Ident, Path)>);
Expand Down
Loading

0 comments on commit bd2c4f9

Please sign in to comment.