Skip to content

Commit

Permalink
Add runtime_pattern macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Lancern authored and SpriteOvO committed Mar 9, 2024
1 parent 93377a8 commit 71b4fd2
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 271 deletions.
62 changes: 60 additions & 2 deletions spdlog-internal/src/pattern_parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,37 @@ use thiserror::Error;
use super::PatternKind;
use crate::impossible;

#[derive(Error, Debug)]
#[derive(Error, Debug, PartialEq)]
pub enum Error {
ConflictName {
existing: PatternKind<()>,
incoming: PatternKind<()>,
},
Template(TemplateError),
Parse(NomError<String>),
Multiple(Vec<Error>),
#[cfg(test)]
__ForInternalTestsUseOnly(usize),
}

impl Error {
pub fn push_err<T>(result: Result<T>, new: Self) -> Result<T> {
match result {
Ok(_) => Err(new),
Err(Self::Multiple(mut errors)) => {
errors.push(new);
Err(Self::Multiple(errors))
}
Err(prev) => Err(Error::Multiple(vec![prev, new])),
}
}

pub fn push_result<T, N>(result: Result<T>, new: Result<N>) -> Result<T> {
match new {
Ok(_) => result,
Err(err) => Self::push_err(result, err),
}
}
}

impl Display for Error {
Expand Down Expand Up @@ -44,11 +67,22 @@ impl Display for Error {
Error::Parse(err) => {
write!(f, "failed to parse template string: {}", err)
}
Error::Multiple(errs) => {
writeln!(f, "{} errors detected:", errs.len())?;
for err in errs {
writeln!(f, " - {}", err)?;
}
Ok(())
}
#[cfg(test)]
Error::__ForInternalTestsUseOnly(value) => {
write!(f, "{}", value)
}
}
}
}

#[derive(Error, Debug)]
#[derive(Error, Debug, Eq, PartialEq)]
pub enum TemplateError {
WrongPatternKindReference {
is_built_as_custom: bool,
Expand Down Expand Up @@ -104,3 +138,27 @@ impl Display for TemplateError {
}

pub type Result<T> = std::result::Result<T, Error>;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn push_err() {
macro_rules! make_err {
( $($inputs:tt)+ ) => {
Error::__ForInternalTestsUseOnly($($inputs)*)
};
}

assert!(matches!(
Error::push_err(Ok(()), make_err!(1)),
Err(make_err!(1))
));

assert!(matches!(
Error::push_err::<()>(Err(make_err!(1)), make_err!(2)),
Err(Error::Multiple(v)) if matches!(v[..], [make_err!(1), make_err!(2)])
));
}
}
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
174 changes: 174 additions & 0 deletions spdlog-internal/src/pattern_parser/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{
borrow::Cow,
collections::{hash_map::Entry, HashMap},
fmt::Debug,
hash::Hash,
};

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

pub fn check_custom_pattern_names<N, I>(names: I) -> Result<()>
where
N: AsRef<str> + Eq + PartialEq + Hash,
I: IntoIterator<Item = N>,
{
let mut seen_names: HashMap<N, usize> = HashMap::new();
let mut result = Ok(());

for name in names {
if let Some(existing) = BuiltInFormatter::iter().find(|f| f.placeholder() == name.as_ref())
{
result = Error::push_err(
result,
Error::ConflictName {
existing: PatternKind::BuiltIn(existing),
incoming: PatternKind::Custom {
placeholder: Cow::Owned(name.as_ref().into()),
factory: (),
},
},
);
}

if let Some(seen_count) = seen_names.get_mut(&name) {
*seen_count += 1;
if *seen_count == 2 {
let conflict_pattern = PatternKind::Custom {
placeholder: Cow::Owned(name.as_ref().into()),
factory: (),
};
result = Error::push_err(
result,
Error::ConflictName {
existing: conflict_pattern.clone(),
incoming: conflict_pattern,
},
);
}
} else {
seen_names.insert(name, 1);
}
}

debug_assert!(seen_names.iter().all(|(_, seen_count)| *seen_count == 1) || result.is_err());

result
}

#[cfg(test)]
mod tests {
use super::*;
use crate::pattern_parser::BuiltInFormatterInner;

#[test]
fn custom_pattern_names_checker() {
use check_custom_pattern_names as check;

assert!(check(["a", "b"]).is_ok());
assert_eq!(
check(["a", "a"]),
Err(Error::ConflictName {
existing: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
},
incoming: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
}
})
);
assert_eq!(
check(["a", "b", "a"]),
Err(Error::ConflictName {
existing: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
},
incoming: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
}
})
);
assert_eq!(
check(["date"]),
Err(Error::ConflictName {
existing: PatternKind::BuiltIn(BuiltInFormatter(BuiltInFormatterInner::Date)),
incoming: PatternKind::Custom {
placeholder: "date".into(),
factory: ()
}
})
);
assert_eq!(
check(["date", "a", "a"]),
Err(Error::Multiple(vec![
Error::ConflictName {
existing: PatternKind::BuiltIn(BuiltInFormatter(BuiltInFormatterInner::Date)),
incoming: PatternKind::Custom {
placeholder: "date".into(),
factory: ()
}
},
Error::ConflictName {
existing: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
},
incoming: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
}
}
]))
);
assert_eq!(
check(["date", "a", "a", "a"]),
Err(Error::Multiple(vec![
Error::ConflictName {
existing: PatternKind::BuiltIn(BuiltInFormatter(BuiltInFormatterInner::Date)),
incoming: PatternKind::Custom {
placeholder: "date".into(),
factory: ()
}
},
Error::ConflictName {
existing: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
},
incoming: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
}
}
]))
);
assert_eq!(
check(["b", "date", "a", "b", "a", "a"]),
Err(Error::Multiple(vec![
Error::ConflictName {
existing: PatternKind::BuiltIn(BuiltInFormatter(BuiltInFormatterInner::Date)),
incoming: PatternKind::Custom {
placeholder: "date".into(),
factory: ()
}
},
Error::ConflictName {
existing: PatternKind::Custom {
placeholder: "b".into(),
factory: ()
},
incoming: PatternKind::Custom {
placeholder: "b".into(),
factory: ()
}
},
Error::ConflictName {
existing: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
},
incoming: PatternKind::Custom {
placeholder: "a".into(),
factory: ()
}
}
]))
);
}
}
16 changes: 15 additions & 1 deletion spdlog-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@
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))
}

#[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);
into_or_error(pattern::runtime_pattern_impl(runtime_pattern))
}

match pattern::pattern_impl(pattern) {
fn into_or_error(result: Result<TokenStream2>) -> TokenStream {
match result {
Ok(stream) => stream.into(),
Err(err) => panic!("{}", err),
}
Expand Down
Loading

0 comments on commit 71b4fd2

Please sign in to comment.