math_symbols/
lib.rs

1//! Named symbols for use in compute algebra systems. Symbol names are
2//! stored centrally such that copies are cheap and need little
3//! memory.
4//!
5//! # Example
6//!
7//! ```rust
8//! use math_symbols::*;
9//!
10//! // Define a number of symbols with variable name equal to symbol name
11//! symbols!(x, y, z);
12//! assert_eq!(x.name(), "x");
13//! assert_eq!(y.name(), "y");
14//! assert_eq!(z.name(), "z");
15//!
16//! // Symbols are identified by their names
17//! let xx = Symbol::new("x");
18//! assert_eq!(x, xx);
19//!
20//! // Symbols are ordered by their creation time
21//! assert!(x < y);
22//!
23//! // We can generate symbol names dynamically. The allocated
24//! // memory will be leaked if and only if a new symbol has to be created.
25//! for i in 0..5 {
26//!     // leaks memory
27//!     let xi = Symbol::new(format!("x{i}"));
28//! }
29//!
30//! for i in 0..5 {
31//!     // does not leak any additional memory
32//!     let xi = Symbol::new(format!("x{i}"));
33//! }
34//! ```
35//! # Features
36//!
37//! - `serde` adds support for (de)serialisation with
38//!   [serde](https://serde.rs/)
39//! - By default each symbol is internally stored in a `u32`. Hence,
40//!   the total number of symbols is limited to 2^32. The features `u8,
41//!   u16, u64, u128, usize` enable symbols `Symbolu8, Symbolu16,
42//!   Symbolu64, Symbolu128, Symbolusize`, and macros `symbols_u8,
43//!   symbols_u16, symbols_u64, symbols_u128, symbols_usize` for the
44//!   corresponding storage type.
45//!
46//! # Similar crates
47//!
48//! - [symbol](https://crates.io/crates/symbol)
49//! - [symbolica](https://symbolica.io) uses a similar symbol storage mechanism.
50//!
51use std::{
52    borrow::Cow,
53    fmt::{self, Display},
54    sync::{LazyLock, Mutex},
55};
56
57use ahash::AHashMap;
58use append_only_vec::AppendOnlyVec;
59use paste::paste;
60
61#[macro_export]
62macro_rules! impl_symbol {
63    ( $( $t:tt ),* ) => {
64        $(
65            paste! {
66                #[derive(Debug, Default)]
67                struct [<SymbolRegister $t>] {
68                    names: AppendOnlyVec<&'static str>,
69                    indices: Mutex<AHashMap<&'static str, $t>>,
70                }
71
72                impl [<SymbolRegister $t>] {
73                    fn new() -> Self {
74                        Self {
75                            names: AppendOnlyVec::new(),
76                            indices: Mutex::new(AHashMap::new()),
77                        }
78                    }
79
80                    fn name(&self, idx: usize) -> &'static str {
81                        &self.names[idx]
82                    }
83
84                    fn idx<S>(&self, name: S) -> $t
85                    where
86                        S: AsRef<str> + Leak<&'static str>,
87                    {
88                        let mut indices = self.indices.lock().unwrap();
89                        if let Some(idx) = indices.get(name.as_ref()) {
90                            return *idx;
91                        }
92                        let name = name.leak();
93                        let new_idx = self.names.len() as _;
94                        indices.insert(name, new_idx);
95                        // Here, we are still protected by the mutex.
96                        // this is necessary to ensure to synchronise the position
97                        // inserted into `indices` with the length of `names`
98                        self.names.push(name);
99                        new_idx
100                    }
101                }
102
103                static [<SYMBOL_REGISTER_ $t:upper>]: LazyLock<[<SymbolRegister $t>]> =
104                    LazyLock::new(|| [<SymbolRegister $t>]::new());
105
106                /// A symbol
107                #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
108                #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109                #[cfg_attr(feature = "serde", serde(transparent))]
110                pub struct [<Symbol $t>] {
111                    #[cfg_attr(
112                        feature = "serde",
113                        serde(
114                            serialize_with = "serialize_sym_" $t,
115                            deserialize_with = "deserialize_sym_"  $t
116                        )
117                    )]
118                    idx: $t,
119                }
120
121                impl [<Symbol $t>] {
122                    /// Construct a symbol with the given name
123                    pub fn new<S>(name: S) -> Self
124                    where S: AsRef<str> + Leak<&'static str>,
125                    {
126                        let idx = [<SYMBOL_REGISTER_ $t:upper>].idx(name);
127                        Self { idx }
128                    }
129
130                    /// Construct a symbol with the given name from a boxed string
131                    pub fn new_from_box(name: Box<str>) -> Self {
132                        let inner: &'static str = Box::leak(name);
133                        let idx = [<SYMBOL_REGISTER_ $t:upper>].idx(inner);
134                        Self { idx }
135                    }
136
137                    /// Construct a symbol with the given name from a string slice
138                    ///
139                    /// If no symbol with the same name is known, this
140                    /// will require an allocation and leak the
141                    /// allocated memory. Prefer [new](Self::new) for
142                    /// string slices with `static` lifetimes.
143                    pub fn new_from_str<'a>(name: &'a str) -> Self {
144                        Self::new(StrWrapper(name))
145                    }
146
147                    /// Get the symbol's name
148                    pub fn name(&self) -> &'static str {
149                        [<SYMBOL_REGISTER_ $t:upper>].name(self.idx as _)
150                    }
151                }
152
153                impl Display for [<Symbol $t>] {
154                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155                        write!(f, "{}", [<SYMBOL_REGISTER_ $t:upper>].name(self.idx as _))
156                    }
157                }
158
159                #[cfg(feature = "serde")]
160                fn [<serialize_sym_ $t>]<S: serde::Serializer>(
161                    sym: &$t,
162                    s: S,
163                ) -> Result<S::Ok, S::Error> {
164                    use serde::Serialize;
165                    let sym = [<Symbol $t>] { idx: *sym };
166                    let name = sym.name();
167                    str::serialize(name, s)
168                }
169
170                #[cfg(feature = "serde")]
171                fn [<deserialize_sym_ $t>]<'de, D: serde::Deserializer<'de>>(
172                    d: D,
173                ) -> Result<$t, D::Error> {
174                    use serde::Deserialize;
175                    let name = String::deserialize(d)?;
176                    let s = [<Symbol $t>]::new(name);
177                    Ok(s.idx)
178                }
179            }
180        )*
181    };
182}
183
184#[cfg(feature = "u8")]
185impl_symbol!(u8);
186
187#[cfg(feature = "u16")]
188impl_symbol!(u16);
189
190#[cfg(feature = "u32")]
191impl_symbol!(u32);
192
193#[cfg(feature = "u64")]
194impl_symbol!(u64);
195
196#[cfg(feature = "u128")]
197impl_symbol!(u128);
198
199#[cfg(feature = "usize")]
200impl_symbol!(usize);
201
202#[cfg(feature = "u32")]
203pub type Symbol = Symbolu32;
204
205/// A type that can be leaked into a `T`
206pub trait Leak<T> {
207    fn leak(self) -> T;
208}
209
210impl Leak<&'static str> for &'static str {
211    fn leak(self) -> &'static str {
212        self
213    }
214}
215
216impl Leak<&'static str> for String {
217    fn leak(self) -> &'static str {
218        String::leak(self)
219    }
220}
221
222impl Leak<&'static str> for Cow<'static, str> {
223    fn leak(self) -> &'static str {
224        match self {
225            Cow::Borrowed(s) => s,
226            Cow::Owned(s) => s.leak(),
227        }
228    }
229}
230
231/// Construct variables with the same variable and symbol name
232#[cfg(feature = "u32")]
233#[macro_export]
234macro_rules! symbols {
235    ( $( $x:ident ),* ) => {
236        $(
237            let $x = $crate::Symbol::new(stringify!($x));
238        )*
239    };
240}
241
242// TODO: put this inside `impl_symbol!`
243
244/// Construct variables with the same variable and symbol name
245#[cfg(feature = "u8")]
246#[macro_export]
247macro_rules! symbols_u8 {
248    ( $( $x:ident ),* ) => {
249        $(
250            let $x = $crate::Symbolu8::new(stringify!($x));
251        )*
252    };
253}
254
255/// Construct variables with the same variable and symbol name
256#[cfg(feature = "u16")]
257#[macro_export]
258macro_rules! symbols_u16 {
259    ( $( $x:ident ),* ) => {
260        $(
261            let $x = $crate::Symbolu16::new(stringify!($x));
262        )*
263    };
264}
265
266/// Construct variables with the same variable and symbol name
267#[cfg(feature = "u32")]
268#[macro_export]
269macro_rules! symbols_u32 {
270    ( $( $x:ident ),* ) => {
271        $(
272            let $x = $crate::Symbolu32::new(stringify!($x));
273        )*
274    };
275}
276
277/// Construct variables with the same variable and symbol name
278#[cfg(feature = "u64")]
279#[macro_export]
280macro_rules! symbols_u64 {
281    ( $( $x:ident ),* ) => {
282        $(
283            let $x = $crate::Symbolu64::new(stringify!($x));
284        )*
285    };
286}
287
288#[cfg(feature = "u128")]
289/// Construct variables with the same variable and symbol name
290#[macro_export]
291macro_rules! symbols_u128 {
292    ( $( $x:ident ),* ) => {
293        $(
294            let $x = $crate::Symbolu128::new(stringify!($x));
295        )*
296    };
297}
298
299/// Construct variables with the same variable and symbol name
300#[cfg(feature = "usize")]
301#[macro_export]
302macro_rules! symbols_usize {
303    ( $( $x:ident ),* ) => {
304        $(
305            let $x = $crate::Symbolusize::new(stringify!($x));
306        )*
307    };
308}
309
310struct StrWrapper<'a>(&'a str);
311
312impl<'a> Leak<&'static str> for StrWrapper<'a> {
313    fn leak(self) -> &'static str {
314        self.0.to_owned().leak()
315    }
316}
317
318impl<'a> AsRef<str> for StrWrapper<'a> {
319    fn as_ref(&self) -> &str {
320        &self.0
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn symbol() {
330        symbols!(x, y, z);
331        assert_eq!(x.name(), "x");
332        assert_eq!(y.name(), "y");
333        assert_eq!(z.name(), "z");
334        let xx = Symbol::new("x");
335        assert_eq!(xx.name(), "x");
336    }
337
338    #[test]
339    fn owned() {
340        symbols!(x);
341        let xx = Symbol::new("x".to_owned());
342        assert_eq!(xx, x);
343    }
344
345    #[test]
346    fn owned_box() {
347        symbols!(x);
348        let xx = Symbol::new_from_box("x".to_owned().into_boxed_str());
349        assert_eq!(xx, x);
350    }
351
352    #[test]
353    fn cow() {
354        let x = Symbol::new(Cow::Owned("x".to_owned()));
355        assert_eq!(x.name(), "x");
356        let xx = Symbol::new(Cow::Borrowed("x"));
357        assert_eq!(xx, x);
358    }
359
360    #[cfg(feature = "u8")]
361    #[test]
362    fn symbol_u8() {
363        symbols_u8!(x, y, z);
364        assert_eq!(x.name(), "x");
365        assert_eq!(y.name(), "y");
366        assert_eq!(z.name(), "z");
367        let xx = Symbolu8::new("x");
368        assert_eq!(xx.name(), "x");
369    }
370
371    #[test]
372    fn symbol_str() {
373        symbols!(x);
374        let xx = {
375            let x = "x".to_owned();
376            Symbol::new_from_str(&x)
377        };
378        assert_eq!(x, xx);
379    }
380}