use std::any::TypeId;
use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::MutexGuard;
use aho_corasick::{AhoCorasick, MatchKind};
use crate::pretty::{combined_pretty_regex, pretty_pattern, pretty_replace_all};
#[derive(Debug, Clone)]
pub(crate) enum Alias {
Name(String),
Number(usize),
}
impl fmt::Display for Alias {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Alias::Name(name) => write!(f, "{}", name),
Alias::Number(num) => write!(f, "#{:03}", num),
}
}
}
#[derive(Clone)]
pub(crate) struct Repr {
alias: Alias,
prefix: Option<String>,
brackets: (&'static str, &'static str),
#[cfg_attr(not(feature = "tracing"), allow(dead_code))]
type_id: TypeId,
}
impl fmt::Display for Repr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (open, close) = self.brackets;
if let Some(prefix) = &self.prefix {
write!(f, "{open}{prefix}|{}{close}", self.alias)
} else {
write!(f, "{open}{}{close}", self.alias)
}
}
}
enum DebugMatcher {
Stale,
Empty,
Built {
ac: AhoCorasick,
replacements: Vec<String>,
},
}
enum PrettyMatcher {
Stale,
Empty,
Built {
regex: regex::Regex,
replacements: Vec<String>,
},
}
pub(crate) struct AliasData {
brackets: (&'static str, &'static str),
numbers: BTreeMap<TypeId, BTreeMap<String, usize>>,
debug_names: BTreeMap<String, Repr>,
pretty_names: BTreeMap<String, Repr>,
prefixes: BTreeMap<TypeId, String>,
debug_matcher: DebugMatcher,
pretty_matcher: PrettyMatcher,
}
impl Default for AliasData {
fn default() -> Self {
Self {
brackets: ("⟪", "⟫"),
numbers: BTreeMap::default(),
debug_names: BTreeMap::default(),
pretty_names: BTreeMap::default(),
prefixes: BTreeMap::default(),
debug_matcher: DebugMatcher::Stale,
pretty_matcher: PrettyMatcher::Stale,
}
}
}
impl AliasData {
fn invalidate(&mut self) {
self.debug_matcher = DebugMatcher::Stale;
self.pretty_matcher = PrettyMatcher::Stale;
}
fn insert_repr(&mut self, debug_key: String, pretty_key: String, repr: Repr) {
let _prev_debug = self.debug_names.insert(debug_key, repr.clone());
let _prev_pretty = self.pretty_names.insert(pretty_key, repr.clone());
#[cfg(feature = "tracing")]
{
if let Some(existing) = _prev_debug
&& existing.type_id != repr.type_id
{
tracing::warn!(
"Alias collision: new alias `{repr}` overshadows `{existing}` because they both have the same Debug formatting"
);
}
if let Some(existing) = _prev_pretty
&& existing.type_id != repr.type_id
{
tracing::warn!(
"Alias collision: new alias `{repr}` overshadows `{existing}` because they both have the same pretty (`{{:#?}}`) formatting"
);
}
}
self.invalidate();
}
fn aliased_debug(&mut self, rep: &str) -> String {
if let DebugMatcher::Stale = self.debug_matcher {
self.debug_matcher = if self.debug_names.is_empty() {
DebugMatcher::Empty
} else {
let mut keys = Vec::with_capacity(self.debug_names.len());
let mut replacements = Vec::with_capacity(self.debug_names.len());
for (key, repr) in &self.debug_names {
keys.push(key.clone());
replacements.push(repr.to_string());
}
let ac = AhoCorasick::builder()
.match_kind(MatchKind::LeftmostLongest)
.build(&keys)
.expect("aho-corasick build from registered keys");
DebugMatcher::Built { ac, replacements }
};
}
match &self.debug_matcher {
DebugMatcher::Built { ac, replacements } => ac.replace_all(rep, replacements),
_ => rep.to_string(),
}
}
fn aliased_pretty(&mut self, rep: &str) -> String {
if let PrettyMatcher::Stale = self.pretty_matcher {
self.pretty_matcher = if self.pretty_names.is_empty() {
PrettyMatcher::Empty
} else {
let mut entries: Vec<_> = self.pretty_names.iter().collect();
entries.sort_by(|(a, _), (b, _)| b.len().cmp(&a.len()).then_with(|| a.cmp(b)));
let mut patterns = Vec::with_capacity(entries.len());
let mut replacements = Vec::with_capacity(entries.len());
for (key, repr) in entries {
patterns.push(pretty_pattern(key));
replacements.push(repr.to_string());
}
let regex = combined_pretty_regex(&patterns);
PrettyMatcher::Built {
regex,
replacements,
}
};
}
match &self.pretty_matcher {
PrettyMatcher::Built {
regex,
replacements,
} => pretty_replace_all(regex, rep, replacements),
_ => rep.to_string(),
}
}
}
#[derive(Clone)]
pub struct AliasContext(Arc<Mutex<AliasData>>);
impl AliasContext {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(AliasData::default())))
}
pub fn set_brackets(&self, brackets: (&'static str, &'static str)) {
let mut lock = self.lock();
lock.brackets = brackets;
lock.invalidate();
}
pub(crate) fn lock(&self) -> MutexGuard<'_, AliasData> {
self.0.lock().expect("AliasContext mutex poisoned")
}
}
impl Default for AliasContext {
fn default() -> Self {
Self::new()
}
}
pub(crate) fn set_prefix(ctx: &AliasContext, type_id: TypeId, prefix: &str) {
let mut lock = ctx.lock();
if lock.prefixes.values().any(|v| v == prefix) {
#[cfg(feature = "tracing")]
tracing::warn!("There is already a type with prefix `{prefix}`");
}
if let Some(_existing) = lock.prefixes.insert(type_id, prefix.to_string()) {
#[cfg(feature = "tracing")]
if _existing != prefix {
tracing::warn!("Prefix collision: overwriting `{_existing}` with `{prefix}`");
}
}
lock.invalidate();
}
pub(crate) fn register_numbered(
ctx: &AliasContext,
type_id: TypeId,
debug_key: String,
pretty_key: String,
) {
let mut lock = ctx.lock();
let counter = lock.numbers.entry(type_id).or_default();
let number = counter.len();
match counter.entry(debug_key.clone()) {
std::collections::btree_map::Entry::Occupied(_e) => {
#[cfg(feature = "tracing")]
tracing::warn!(
"Cannot alias_numbered more than once: existing alias is `{}`",
_e.get(),
);
return;
}
std::collections::btree_map::Entry::Vacant(e) => {
e.insert(number);
}
}
let prefix = lock.prefixes.get(&type_id).cloned();
let repr = Repr {
alias: Alias::Number(number),
prefix,
brackets: lock.brackets,
type_id,
};
lock.insert_repr(debug_key, pretty_key, repr);
}
pub(crate) fn register_named(
ctx: &AliasContext,
type_id: TypeId,
debug_key: String,
pretty_key: String,
name: &str,
) {
let mut lock = ctx.lock();
let prefix = lock.prefixes.get(&type_id).cloned();
let repr = Repr {
alias: Alias::Name(name.to_string()),
prefix,
brackets: lock.brackets,
type_id,
};
lock.insert_repr(debug_key, pretty_key, repr);
}
pub(crate) fn fmt_aliased<T: ?Sized + fmt::Debug>(
val: &T,
ctx: &AliasContext,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let mut lock = ctx.lock();
let rendered = if f.alternate() {
let rep = format!("{:#?}", val);
lock.aliased_pretty(&rep)
} else {
let rep = format!("{:?}", val);
lock.aliased_debug(&rep)
};
write!(f, "{}", rendered)
}