Skip to main content

litcheck_core/symbol/
mod.rs

1use core::{fmt, mem, ops::Deref, str};
2use std::collections::BTreeMap;
3
4pub use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
5
6pub mod symbols {
7    include!(env!("SYMBOLS_RS"));
8}
9
10static SYMBOL_TABLE: std::sync::LazyLock<SymbolTable> =
11    std::sync::LazyLock::new(SymbolTable::default);
12
13#[derive(Default)]
14struct SymbolTable {
15    interner: RwLock<Interner>,
16}
17
18/// A symbol is an interned string.
19#[derive(Clone, Copy, PartialEq, Eq, Hash)]
20pub struct Symbol(SymbolIndex);
21
22impl serde::Serialize for Symbol {
23    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24    where
25        S: serde::Serializer,
26    {
27        self.as_str().serialize(serializer)
28    }
29}
30
31impl<'de> serde::Deserialize<'de> for Symbol {
32    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33    where
34        D: serde::Deserializer<'de>,
35    {
36        struct SymbolVisitor;
37        impl serde::de::Visitor<'_> for SymbolVisitor {
38            type Value = Symbol;
39
40            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
41                formatter.write_str("symbol")
42            }
43
44            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
45            where
46                E: serde::de::Error,
47            {
48                Ok(Symbol::intern(v))
49            }
50        }
51        deserializer.deserialize_str(SymbolVisitor)
52    }
53}
54
55impl Symbol {
56    #[inline]
57    pub const fn new(n: u32) -> Self {
58        Self(SymbolIndex::new(n))
59    }
60
61    /// Maps a string to its interned representation.
62    pub fn intern(string: impl ToString) -> Self {
63        let string = string.to_string();
64        with_interner(|interner| interner.intern(string))
65    }
66
67    pub fn as_str(self) -> &'static str {
68        with_read_only_interner(|interner| unsafe {
69            // This is safe because the interned string will live for the
70            // lifetime of the program
71            mem::transmute::<&str, &'static str>(interner.get(self))
72        })
73    }
74
75    #[inline]
76    pub fn as_u32(self) -> u32 {
77        self.0.as_u32()
78    }
79
80    #[inline]
81    pub fn as_usize(self) -> usize {
82        self.0.as_usize()
83    }
84
85    /// Returns true if this symbol is a keyword in the IR textual format
86    #[inline]
87    pub fn is_keyword(self) -> bool {
88        symbols::is_keyword(self)
89    }
90}
91impl fmt::Debug for Symbol {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        write!(f, "{}({:?})", self, self.0)
94    }
95}
96impl fmt::Display for Symbol {
97    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98        fmt::Display::fmt(&self.as_str(), f)
99    }
100}
101impl PartialOrd for Symbol {
102    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
103        Some(self.cmp(other))
104    }
105}
106impl Ord for Symbol {
107    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
108        self.as_str().cmp(other.as_str())
109    }
110}
111impl AsRef<str> for Symbol {
112    #[inline(always)]
113    fn as_ref(&self) -> &str {
114        self.as_str()
115    }
116}
117impl core::borrow::Borrow<str> for Symbol {
118    #[inline(always)]
119    fn borrow(&self) -> &str {
120        self.as_str()
121    }
122}
123impl<T: Deref<Target = str>> PartialEq<T> for Symbol {
124    fn eq(&self, other: &T) -> bool {
125        self.as_str() == other.deref()
126    }
127}
128impl From<&'static str> for Symbol {
129    fn from(s: &'static str) -> Self {
130        with_interner(|interner| interner.insert(s))
131    }
132}
133impl From<String> for Symbol {
134    fn from(s: String) -> Self {
135        Self::intern(s)
136    }
137}
138impl From<Box<str>> for Symbol {
139    fn from(s: Box<str>) -> Self {
140        Self::intern(s)
141    }
142}
143impl From<std::borrow::Cow<'static, str>> for Symbol {
144    fn from(s: std::borrow::Cow<'static, str>) -> Self {
145        use std::borrow::Cow;
146        match s {
147            Cow::Borrowed(s) => s.into(),
148            Cow::Owned(s) => Self::intern(s),
149        }
150    }
151}
152impl From<compact_str::CompactString> for Symbol {
153    fn from(s: compact_str::CompactString) -> Self {
154        Self::intern(s.into_string())
155    }
156}
157
158#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
159struct SymbolIndex(u32);
160impl SymbolIndex {
161    // shave off 256 indices at the end to allow space for packing these indices into enums
162    pub const MAX_AS_U32: u32 = 0xffff_ff00;
163
164    #[inline]
165    const fn new(n: u32) -> Self {
166        assert!(n <= Self::MAX_AS_U32, "out of range value used");
167
168        SymbolIndex(n)
169    }
170
171    #[inline]
172    pub fn as_u32(self) -> u32 {
173        self.0
174    }
175
176    #[inline]
177    pub fn as_usize(self) -> usize {
178        self.0 as usize
179    }
180}
181impl From<SymbolIndex> for u32 {
182    #[inline]
183    fn from(v: SymbolIndex) -> u32 {
184        v.as_u32()
185    }
186}
187impl From<SymbolIndex> for usize {
188    #[inline]
189    fn from(v: SymbolIndex) -> usize {
190        v.as_usize()
191    }
192}
193
194struct Interner {
195    pub names: BTreeMap<&'static str, Symbol>,
196    pub strings: Vec<&'static str>,
197}
198
199impl Default for Interner {
200    fn default() -> Self {
201        let mut this = Self {
202            names: BTreeMap::default(),
203            strings: Vec::with_capacity(symbols::__SYMBOLS.len()),
204        };
205        for (sym, s) in symbols::__SYMBOLS {
206            this.names.insert(s, *sym);
207            this.strings.push(s);
208        }
209        this
210    }
211}
212
213impl Interner {
214    pub fn intern(&mut self, string: String) -> Symbol {
215        if let Some(&name) = self.names.get(string.as_str()) {
216            return name;
217        }
218
219        let name = Symbol::new(self.strings.len() as u32);
220
221        let string = string.into_boxed_str();
222        let string: &'static str = Box::leak(string);
223        self.strings.push(string);
224        self.names.insert(string, name);
225        name
226    }
227
228    pub fn insert(&mut self, s: &'static str) -> Symbol {
229        if let Some(&name) = self.names.get(s) {
230            return name;
231        }
232        let name = Symbol::new(self.strings.len() as u32);
233        self.strings.push(s);
234        self.names.insert(s, name);
235        name
236    }
237
238    pub fn get(&self, symbol: Symbol) -> &'static str {
239        self.strings[symbol.0.as_usize()]
240    }
241}
242
243// If an interner exists, return it. Otherwise, prepare a fresh one.
244#[inline]
245fn with_interner<T, F: FnOnce(&mut Interner) -> T>(f: F) -> T {
246    let mut table = SYMBOL_TABLE.interner.write();
247    f(&mut table)
248}
249
250#[inline]
251fn with_read_only_interner<T, F: FnOnce(&Interner) -> T>(f: F) -> T {
252    let table = SYMBOL_TABLE.interner.read();
253    f(&table)
254}