use phf::{Set as PhfSet, phf_set};
use crate::{Allocator, FxHashSet};
static WELL_KNOWN: PhfSet<&'static str> = phf_set! {
"a", "abbr", "address", "area", "article", "aside", "audio",
"b", "base", "bdi", "bdo", "blockquote", "body", "br", "button",
"canvas", "caption", "cite", "code", "col", "colgroup",
"data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt",
"em", "embed",
"fieldset", "figcaption", "figure", "footer", "form",
"h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
"i", "iframe", "img", "input", "ins",
"kbd",
"label", "legend", "li", "link",
"main", "map", "mark", "menu", "meta", "meter",
"nav", "noscript",
"object", "ol", "optgroup", "option", "output",
"p", "param", "picture", "pre", "progress",
"q",
"rp", "rt", "ruby",
"s", "samp", "script", "search", "section", "select", "small", "source",
"span", "strong", "style", "sub", "summary", "sup",
"table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead",
"time", "title", "tr", "track",
"u", "ul",
"var", "video",
"wbr",
"circle", "clipPath", "defs", "ellipse", "foreignObject", "g",
"line", "linearGradient", "marker", "mask", "path", "pattern",
"polygon", "polyline", "radialGradient", "rect", "stop", "svg",
"symbol", "text", "textPath", "tspan", "use", "view",
"math", "mi", "mn", "mo", "mrow", "ms", "msqrt", "mtext",
"component", "keep-alive", "KeepAlive", "slot", "suspense", "Suspense",
"teleport", "Teleport", "transition", "Transition",
"transition-group", "TransitionGroup",
"bind", "cloak", "else", "else-if", "for", "if", "is", "memo",
"model", "on", "once", "show",
"class", "id", "key", "name", "ref", "src", "type", "value",
"checked", "disabled", "href", "placeholder", "selected",
};
#[inline]
#[must_use]
pub fn well_known_atom(name: &str) -> Option<&'static str> {
WELL_KNOWN.get_key(name).copied()
}
pub struct Interner<'a> {
allocator: &'a Allocator,
seen: FxHashSet<&'a str>,
}
impl<'a> Interner<'a> {
#[inline]
#[must_use]
pub fn new(allocator: &'a Allocator) -> Self {
Self {
allocator,
seen: FxHashSet::default(),
}
}
#[inline]
pub fn intern(&mut self, name: &str) -> &'a str {
if let Some(atom) = well_known_atom(name) {
return atom;
}
if let Some(existing) = self.seen.get(name) {
return existing;
}
let atom: &'a str = self.allocator.alloc_str(name);
self.seen.insert(atom);
atom
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.seen.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.seen.is_empty()
}
}
impl std::fmt::Debug for Interner<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Interner")
.field("interned", &self.seen.len())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::{Interner, well_known_atom};
use crate::Allocator;
#[test]
fn well_known_names_resolve_to_static_literals() {
assert_eq!(well_known_atom("div"), Some("div"));
assert_eq!(well_known_atom("bind"), Some("bind"));
assert_eq!(well_known_atom("class"), Some("class"));
assert_eq!(well_known_atom("MyComponent"), None);
}
#[test]
fn well_known_names_never_touch_the_arena() {
let allocator = Allocator::new();
let mut interner = Interner::new(&allocator);
assert_eq!(interner.intern("div"), "div");
assert_eq!(interner.intern("span"), "span");
assert!(interner.is_empty());
assert_eq!(allocator.allocated_bytes(), 0);
}
#[test]
fn computed_names_are_copied_once_and_shared() {
let allocator = Allocator::new();
let mut interner = Interner::new(&allocator);
let first = interner.intern("onCustomEvent");
let second = interner.intern("onCustomEvent");
assert_eq!(first, "onCustomEvent");
assert!(std::ptr::eq(first, second));
assert_eq!(interner.len(), 1);
}
#[test]
fn distinct_names_get_distinct_atoms() {
let allocator = Allocator::new();
let mut interner = Interner::new(&allocator);
let a = interner.intern("alpha");
let b = interner.intern("beta");
assert_eq!((a, b), ("alpha", "beta"));
assert_eq!(interner.len(), 2);
}
#[test]
fn atoms_outlive_the_interner() {
let allocator = Allocator::new();
let atom = {
let mut interner = Interner::new(&allocator);
interner.intern("survives")
};
assert_eq!(atom, "survives");
}
}