Skip to main content

mq_lang/
ident.rs

1use std::sync::{LazyLock, RwLock};
2
3use string_interner::{DefaultBackend, DefaultSymbol, StringInterner};
4
5static STRING_INTERNER: LazyLock<RwLock<StringInterner<DefaultBackend>>> =
6    LazyLock::new(|| RwLock::new(StringInterner::default()));
7
8/// An interned string identifier for efficient storage and comparison.
9///
10/// Identifiers are stored in a global string interner, allowing fast equality
11/// checks and reduced memory usage for frequently used strings.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct Ident(DefaultSymbol);
14
15impl Ident {
16    /// Creates a new interned identifier from a string slice.
17    ///
18    /// If the string already exists in the interner, returns the existing identifier.
19    pub fn new(s: &str) -> Self {
20        Self(STRING_INTERNER.write().unwrap().get_or_intern(s))
21    }
22
23    /// Resolves the identifier to its string representation.
24    ///
25    /// Returns a new `String` with the identifier's content.
26    pub fn as_str(&self) -> String {
27        STRING_INTERNER.read().unwrap().resolve(self.0).unwrap().to_string()
28    }
29
30    /// Resolves the identifier and passes it to a callback function.
31    ///
32    /// This is more efficient than `as_str()` when you don't need to own the string,
33    /// as it avoids allocating a new `String`.
34    pub fn resolve_with<F, R>(&self, f: F) -> R
35    where
36        F: FnOnce(&str) -> R,
37    {
38        let interner = STRING_INTERNER.read().unwrap();
39        let resolved = interner.resolve(self.0).unwrap();
40        f(resolved)
41    }
42}
43
44impl Default for Ident {
45    fn default() -> Self {
46        Ident::new("")
47    }
48}
49
50impl From<&str> for Ident {
51    fn from(s: &str) -> Self {
52        Self::new(s)
53    }
54}
55
56impl From<String> for Ident {
57    fn from(s: String) -> Self {
58        Self::new(&s)
59    }
60}
61
62impl std::fmt::Display for Ident {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        self.resolve_with(|s| write!(f, "{}", s))
65    }
66}
67
68#[cfg(feature = "ast-json")]
69impl serde::Serialize for Ident {
70    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
71    where
72        S: serde::Serializer,
73    {
74        self.as_str().serialize(serializer)
75    }
76}
77
78#[cfg(feature = "ast-json")]
79impl<'de> serde::Deserialize<'de> for Ident {
80    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81    where
82        D: serde::Deserializer<'de>,
83    {
84        let s = String::deserialize(deserializer)?;
85        Ok(Ident::new(&s))
86    }
87}
88
89/// Returns all interned strings currently in the global string interner.
90pub fn all_symbols() -> Vec<String> {
91    STRING_INTERNER
92        .read()
93        .unwrap()
94        .iter()
95        .map(|(_, s)| s.to_string())
96        .collect()
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_ident_new_and_as_str() {
105        let ident = Ident::new("hello");
106        assert_eq!(ident.as_str(), "hello");
107    }
108
109    #[test]
110    fn test_ident_from_str_and_string() {
111        let ident1: Ident = "world".into();
112        let ident2: Ident = String::from("world").into();
113        assert_eq!(ident1, ident2);
114        assert_eq!(ident1.as_str(), "world");
115    }
116
117    #[test]
118    fn test_ident_display_trait() {
119        let ident = Ident::new("display_test");
120        let s = format!("{}", ident);
121        assert_eq!(s, "display_test");
122    }
123
124    #[test]
125    fn test_ident_resolve_with() {
126        let ident = Ident::new("resolve");
127        let len = ident.resolve_with(|s| s.len());
128        assert_eq!(len, "resolve".len());
129    }
130
131    #[cfg(feature = "ast-json")]
132    #[test]
133    fn test_ident_serde() {
134        let ident = Ident::new("serde_test");
135        let serialized = serde_json::to_string(&ident).unwrap();
136        assert_eq!(serialized, "\"serde_test\"");
137        let deserialized: Ident = serde_json::from_str(&serialized).unwrap();
138        assert_eq!(deserialized, ident);
139    }
140}