use serde::{Deserialize, Serialize};
use std::{
borrow::Borrow,
fmt::{self, Debug},
hash::Hash,
sync::{
LazyLock,
atomic::{AtomicUsize, Ordering},
},
};
use crate::{metrics::increment, position::TermPos};
static INTERNER: LazyLock<interner::Interner> = LazyLock::new(interner::Interner::new);
static COUNTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(into = "&'static str", from = "String")]
pub struct Ident(interner::Symbol);
impl Ident {
pub fn new(s: impl AsRef<str>) -> Self {
Self(INTERNER.get_or_intern(s.as_ref()))
}
pub fn label(&self) -> &'static str {
INTERNER.lookup(self.0)
}
pub fn into_label(self) -> String {
self.label().to_owned()
}
#[doc(hidden)]
pub fn find_generated(s: &str) -> Self {
Self(INTERNER.find_generated(s))
}
pub fn fresh() -> Self {
increment!("Ident::fresh");
Self(INTERNER.intern_generated(format!(
"{}{}",
GEN_PREFIX,
COUNTER.fetch_add(1, Ordering::Relaxed)
)))
}
pub fn spanned(self, pos: TermPos) -> LocIdent {
LocIdent { ident: self, pos }
}
pub fn is_generated(&self) -> bool {
INTERNER.is_generated(self.0)
}
}
impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.label())
}
}
impl fmt::Debug for Ident {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_generated() {
write!(f, "`{}` (generated)", self.label())
} else {
write!(f, "`{}`", self.label())
}
}
}
impl From<Ident> for LocIdent {
fn from(ident: Ident) -> Self {
ident.spanned(TermPos::None)
}
}
impl From<&LocIdent> for Ident {
fn from(ident: &LocIdent) -> Self {
ident.ident()
}
}
impl PartialOrd for Ident {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Ident {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.label().cmp(other.label())
}
}
impl<'a> From<&'a str> for Ident {
fn from(s: &'a str) -> Self {
Ident::new(s)
}
}
impl From<String> for Ident {
fn from(s: String) -> Self {
Ident::new(s)
}
}
impl From<Ident> for &'static str {
fn from(id: Ident) -> &'static str {
id.label()
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(into = "String", from = "String")]
pub struct LocIdent {
ident: Ident,
pub pos: TermPos,
}
impl LocIdent {
pub fn new_with_pos(label: impl AsRef<str>, pos: TermPos) -> Self {
Self {
ident: Ident::new(label),
pos,
}
}
pub fn new(label: impl AsRef<str>) -> Self {
Self::new_with_pos(label, TermPos::None)
}
pub fn with_pos(self, pos: TermPos) -> LocIdent {
LocIdent { pos, ..self }
}
pub fn fresh() -> Self {
Ident::fresh().into()
}
pub fn ident(&self) -> Ident {
self.ident
}
pub fn label(&self) -> &'static str {
self.ident.label()
}
pub fn into_label(self) -> String {
self.label().to_owned()
}
pub fn is_generated(&self) -> bool {
self.ident.is_generated()
}
}
pub const GEN_PREFIX: char = '%';
impl PartialOrd for LocIdent {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for LocIdent {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.label().cmp(other.label())
}
}
impl PartialEq for LocIdent {
fn eq(&self, other: &Self) -> bool {
self.ident == other.ident
}
}
impl Eq for LocIdent {}
impl Hash for LocIdent {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.ident.hash(state)
}
}
impl Borrow<Ident> for LocIdent {
fn borrow(&self) -> &Ident {
&self.ident
}
}
impl fmt::Display for LocIdent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.label())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FastOrdIdent(pub Ident);
impl PartialOrd for FastOrdIdent {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FastOrdIdent {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.0.cmp(&other.0.0)
}
}
impl<'a> From<&'a str> for LocIdent {
fn from(s: &'a str) -> Self {
LocIdent::new(s)
}
}
impl From<String> for LocIdent {
fn from(s: String) -> Self {
LocIdent::new(s)
}
}
impl From<LocIdent> for &'static str {
fn from(id: LocIdent) -> &'static str {
id.label()
}
}
impl From<LocIdent> for String {
fn from(id: LocIdent) -> String {
id.label().to_owned()
}
}
impl AsRef<str> for LocIdent {
fn as_ref(&self) -> &str {
self.label()
}
}
mod interner {
use std::collections::HashMap;
use std::sync::{Mutex, RwLock};
use typed_arena::Arena;
use super::Bitmap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Symbol(u32);
pub(crate) struct Interner(RwLock<InnerInterner>);
impl Interner {
pub(crate) fn new() -> Self {
Self(RwLock::new(InnerInterner::empty()))
}
pub(crate) fn get_or_intern(&self, string: impl AsRef<str>) -> Symbol {
self.0.write().unwrap().get_or_intern(string)
}
pub(crate) fn intern_generated(&self, string: impl AsRef<str>) -> Symbol {
self.0.write().unwrap().intern(string, true)
}
pub(crate) fn lookup(&self, sym: Symbol) -> &str {
unsafe { std::mem::transmute::<&'_ str, &'_ str>(self.0.read().unwrap().lookup(sym)) }
}
pub(crate) fn find_generated(&self, s: &str) -> Symbol {
let inner = self.0.read().unwrap();
inner.with(|inner| {
let idx = (0..inner.vec.len())
.find(|&idx| inner.generated[idx] && inner.vec[idx] == s)
.unwrap();
Symbol(idx as u32)
})
}
pub(crate) fn is_generated(&self, sym: Symbol) -> bool {
self.0.read().unwrap().is_generated(sym)
}
}
#[ouroboros::self_referencing]
struct InnerInterner {
arena: Mutex<Arena<u8>>,
#[borrows(arena)]
#[covariant]
map: HashMap<&'this str, Symbol>,
#[borrows(arena)]
#[covariant]
vec: Vec<&'this str>,
generated: Bitmap,
}
impl InnerInterner {
fn empty() -> Self {
Self::new(
Mutex::new(Arena::new()),
|_arena| HashMap::new(),
|_arena| Vec::new(),
Bitmap::default(),
)
}
fn get_or_intern(&mut self, string: impl AsRef<str>) -> Symbol {
if let Some(sym) = self.borrow_map().get(string.as_ref()) {
return *sym;
}
self.intern(string, false)
}
fn intern(&mut self, string: impl AsRef<str>, generated: bool) -> Symbol {
let in_string = unsafe {
std::mem::transmute::<&'_ str, &'_ str>(
self.borrow_arena()
.lock()
.unwrap()
.alloc_str(string.as_ref()),
)
};
let sym = Symbol(self.borrow_vec().len() as u32);
self.with_vec_mut(|v| v.push(in_string));
self.with_generated_mut(|g| g.push(generated));
if !generated {
self.with_map_mut(|m| m.insert(in_string, sym));
}
sym
}
fn lookup(&self, sym: Symbol) -> &str {
self.borrow_vec()[sym.0 as usize]
}
fn is_generated(&self, sym: Symbol) -> bool {
self.borrow_generated()[sym.0 as usize]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intern_then_lookup() {
let interner = Interner::new();
let test_string = "test_string";
let sym = interner.get_or_intern(test_string);
assert_eq!(interner.lookup(sym), test_string);
}
#[test]
fn test_intern_twice_has_same_symbol() {
let interner = Interner::new();
let test_string = "test_string";
let sym1 = interner.get_or_intern(test_string);
let sym2 = interner.get_or_intern(test_string);
assert_eq!(sym1, sym2);
}
#[test]
fn test_intern_two_different_has_different_symbols() {
let interner = Interner::new();
let sym1 = interner.get_or_intern("a");
let sym2 = interner.get_or_intern("b");
assert_ne!(sym1, sym2);
}
#[test]
fn test_large_number_of_interns() {
let interner = Interner::new();
for i in 0..10000 {
let i = i.to_string();
let sym = interner.get_or_intern(&i);
assert_eq!(i, interner.lookup(sym));
}
assert_eq!(10000, interner.0.read().unwrap().borrow_map().len());
assert_eq!(10000, interner.0.read().unwrap().borrow_vec().len());
for i in 0..10000 {
let i = i.to_string();
let sym = interner.get_or_intern(&i);
assert_eq!(i, interner.lookup(sym));
}
assert_eq!(10000, interner.0.read().unwrap().borrow_map().len());
assert_eq!(10000, interner.0.read().unwrap().borrow_vec().len());
}
}
}
#[derive(Default)]
struct Bitmap {
data: Vec<u64>,
len: usize,
}
impl Bitmap {
fn location(idx: usize) -> (usize, u64) {
let shift = (idx % 64) as u32;
(idx / 64, 1 << shift)
}
fn push(&mut self, val: bool) {
let (idx, mask) = Bitmap::location(self.len);
if idx >= self.data.len() {
debug_assert_eq!(idx, self.data.len());
self.data.push(0);
}
if val {
self.data[idx] |= mask;
}
self.len += 1;
}
}
impl std::ops::Index<usize> for Bitmap {
type Output = bool;
fn index(&self, idx: usize) -> &bool {
let (idx, mask) = Bitmap::location(idx);
if (self.data[idx] & mask) == 0 {
&false
} else {
&true
}
}
}
#[cfg(test)]
mod tests {
use super::Bitmap;
#[test]
fn bitmap_basics() {
let mut b = Bitmap::default();
b.push(true);
b.push(false);
assert!(b[0]);
assert!(!b[1]);
for _ in 0..64 {
b.push(true);
}
b.push(false);
assert!(b[63]);
assert!(b[64]);
assert!(b[65]);
assert!(!b[66]);
}
}