wolfram_expr/
symbol.rs

1//! Representation of Wolfram Language symbols.
2//!
3//! This module provides four primary types:
4//!
5//! * [`Symbol`]
6//! * [`SymbolName`]
7//! * [`Context`]
8//! * [`RelativeContext`]
9//!
10//! These types are used for storing a string value that has been validated to conform
11//! to the syntax of Wolfram Language [symbols and contexts][ref/SymbolNamesAndContexts].
12//!
13//! In addition to the previous types, which own their string value, types are provided
14//! that can be used to validate a borrowed `&str` value, without requiring another
15//! allocation:
16//!
17//! * [`SymbolRef`]
18//! * [`SymbolNameRef`]
19//! * [`ContextRef`]
20// * TODO: `RelativeContextRef`
21//!
22//! ## Related Links
23//!
24//! * [Input Syntax: Symbol Names and Contexts][ref/SymbolNamesAndContexts]
25//!
26//! [ref/SymbolNamesAndContexts]: https://reference.wolfram.com/language/tutorial/InputSyntax.html#6562
27
28pub(crate) mod parse;
29
30use std::{
31    fmt::{self, Debug, Display},
32    mem,
33    sync::Arc,
34};
35
36
37/* Notes
38
39Operations on Symbols
40
41- Format (with conditional context path based on $Context)
42- Test for equality
43- Lookup symbol name in context path while parsing
44- Remove / format Removed["..."]
45
46*/
47
48//==========================================================
49// Types
50//==========================================================
51
52//======================================
53// Owned Data
54//======================================
55
56// TODO: Change these types to be Arc<str>. This has the consequence of increasing the
57//       size of these types from 64-bits to 128 bits, so first take care that they are
58//       not passed through a C FFI anywhere as a pointer-sized type.
59
60/// Wolfram Language symbol.
61///
62/// # PartialOrd sorting order
63///
64/// The comparison behavior of this type is **NOT** guaranteed to match the behavior of
65/// `` System`Order `` for symbols (and does *not* match it at the moment).
66///
67/// This type implements `PartialOrd`/`Ord` primarily for the purposes of allowing
68/// instances of this type to be included in ordered sets (e.g. `BTreeMap`).
69#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
70#[repr(C)]
71pub struct Symbol(Arc<String>);
72
73/// The identifier portion of a symbol. This contains no context marks ('`').
74///
75/// In the symbol `` Global`foo ``, the `SymbolName` is `"foo"`.
76#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub struct SymbolName(Arc<String>);
78
79/// Wolfram Language context.
80///
81/// Examples: `` System` ``, `` Global` ``, `` MyPackage`Utils` ``, etc.
82#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
83pub struct Context(Arc<String>);
84
85/// Context begining with a `` ` ``.
86#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub struct RelativeContext(Arc<String>);
88
89// By using `usize` here, we guarantee that we can later change this to be a pointer
90// instead without changing the sizes of a lot of Expr types. This is good for FFI/ABI
91// compatibility if I decide to change the way Symbol works.
92const _: () = assert!(mem::size_of::<Symbol>() == mem::size_of::<usize>());
93const _: () = assert!(mem::align_of::<Symbol>() == mem::align_of::<usize>());
94
95//======================================
96// Borrowed Data
97//======================================
98
99/// Borrowed string containing a valid symbol.
100#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
101pub struct SymbolRef<'s>(&'s str);
102
103/// Borrowing string containing a valid symbol name.
104#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
105pub struct SymbolNameRef<'s>(&'s str);
106
107/// Borrowed string containing a valid context.
108#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
109pub struct ContextRef<'s>(pub(super) &'s str);
110
111//==========================================================
112// Impls -- Owned Types
113//==========================================================
114
115impl From<&Symbol> for Symbol {
116    fn from(sym: &Symbol) -> Self {
117        sym.clone()
118    }
119}
120
121impl Symbol {
122    /// Attempt to parse `input` as an absolute symbol.
123    ///
124    /// An absolute symbol is a symbol with an explicit context path. ``"System`Plus"`` is
125    /// an absolute symbol, ``"Plus"`` is a relative symbol and/or a [`SymbolName`].
126    /// ``"`Plus"`` is also a relative symbol.
127    pub fn try_new(input: &str) -> Option<Self> {
128        let sym_ref = SymbolRef::try_new(input)?;
129
130        Some(sym_ref.to_symbol())
131    }
132
133    /// Construct a symbol from `input`.
134    ///
135    /// # Panics
136    ///
137    /// This function will panic if `input` is not a valid Wolfram Language symbol.
138    /// `Symbol::try_new(input)` must succeed.
139    ///
140    /// This method is intended to be used for convenient construction of symbols from
141    /// string literals, where an error is unlikely to occur, e.g.:
142    ///
143    /// ```
144    /// # use wolfram_expr::{Expr, Symbol};
145    /// let expr = Expr::normal(Symbol::new("MyPackage`Foo"), vec![]);
146    /// ```
147    ///
148    /// If not using a string literal as the argument, prefer to use [`Symbol::try_new`]
149    /// and handle the error condition.
150    #[track_caller]
151    pub fn new(input: &str) -> Self {
152        match Symbol::try_new(input) {
153            Some(symbol) => symbol,
154            None => panic!("string is not parseable as a symbol: {}", input),
155        }
156    }
157
158    /// Get a borrowed [`SymbolRef`] from this [`Symbol`].
159    pub fn as_symbol_ref(&self) -> SymbolRef {
160        let Symbol(arc_string) = self;
161
162        SymbolRef(arc_string.as_str())
163    }
164
165    /// Get the context path part of a symbol as an [`ContextRef`].
166    pub fn context(&self) -> ContextRef {
167        self.as_symbol_ref().context()
168    }
169
170    /// Get the symbol name part of a symbol as a [`SymbolNameRef`].
171    pub fn symbol_name(&self) -> SymbolNameRef {
172        self.as_symbol_ref().symbol_name()
173    }
174}
175
176impl SymbolName {
177    /// Attempt to parse `input` as a symbol name.
178    ///
179    /// A symbol name is a symbol without any context marks.
180    pub fn try_new(input: &str) -> Option<SymbolName> {
181        SymbolNameRef::try_new(input)
182            .as_ref()
183            .map(SymbolNameRef::to_symbol_name)
184    }
185
186    /// Get a borrowed [`SymbolNameRef`] from this `SymbolName`.
187    pub fn as_symbol_name_ref(&self) -> SymbolNameRef {
188        SymbolNameRef(self.as_str())
189    }
190}
191
192impl Context {
193    /// Attempt to parse `input` as a context.
194    pub fn try_new(input: &str) -> Option<Self> {
195        let context_ref = ContextRef::try_new(input)?;
196
197        Some(context_ref.to_context())
198    }
199
200    /// Construct a context from `input`.
201    ///
202    /// # Panics
203    ///
204    /// This function will panic if `input` is not a valid Wolfram Language context.
205    /// `Context::try_new(input)` must succeed.
206    ///
207    /// This method is intended to be used for convenient construction of contexts from
208    /// string literals, where an error is unlikely to occur, e.g.:
209    ///
210    /// ```
211    /// use wolfram_expr::symbol::Context;
212    ///
213    /// let context = Context::new("MyPackage`");
214    /// ```
215    ///
216    /// If not using a string literal as the argument, prefer to use [`Context::try_new`]
217    /// and handle the error condition.
218    #[track_caller]
219    pub fn new(input: &str) -> Self {
220        match Context::try_new(input) {
221            Some(context) => context,
222            None => panic!("string is not parseable as a context: {}", input),
223        }
224    }
225
226    /// The `` Global` `` context.
227    pub fn global() -> Self {
228        Context(Arc::new(String::from("Global`")))
229    }
230
231    /// The `` System` `` context.
232    pub fn system() -> Self {
233        Context(Arc::new(String::from("System`")))
234    }
235
236    /// Construct a new [`Context`] by appending a new context component to this
237    /// context.
238    ///
239    /// ```
240    /// use wolfram_expr::symbol::{Context, SymbolName, SymbolNameRef};
241    ///
242    /// let context = Context::from_symbol_name(&SymbolName::try_new("MyContext").unwrap());
243    /// let private = context.join(SymbolNameRef::try_new("Private").unwrap());
244    ///
245    /// assert!(private.as_str() == "MyContext`Private`");
246    /// ```
247    pub fn join(&self, name: SymbolNameRef) -> Context {
248        let Context(context) = self;
249        Context::try_new(&format!("{}{}`", context, name.as_str()))
250            .expect("Context::join(): invalid Context")
251    }
252
253    /// Return the components of this [`Context`].
254    ///
255    /// ```
256    /// use wolfram_expr::symbol::Context;
257    ///
258    /// let context = Context::new("MyPackage`Sub`Module`");
259    ///
260    /// let components = context.components();
261    ///
262    /// assert!(components.len() == 3);
263    /// assert!(components[0].as_str() == "MyPackage");
264    /// assert!(components[1].as_str() == "Sub");
265    /// assert!(components[2].as_str() == "Module");
266    /// ```
267    pub fn components(&self) -> Vec<SymbolNameRef> {
268        let Context(string) = self;
269
270        let comps: Vec<SymbolNameRef> = string
271            .split('`')
272            // Remove the last component, which will always be the empty string
273            .filter(|comp| !comp.is_empty())
274            .map(|comp| {
275                SymbolNameRef::try_new(comp)
276                    .expect("Context::components(): invalid context component")
277            })
278            .collect();
279
280        comps
281    }
282
283    /// Get a borrowed [`ContextRef`] from this `Context`.
284    pub fn as_context_ref(&self) -> ContextRef {
285        ContextRef(self.as_str())
286    }
287
288    /// Create the context `` name` ``.
289    pub fn from_symbol_name(name: &SymbolName) -> Self {
290        Context::try_new(&format!("{}`", name)).unwrap()
291    }
292}
293
294impl RelativeContext {
295    /// Attempt to parse `input` as a relative context.
296    pub fn try_new(input: &str) -> Option<Self> {
297        crate::symbol::parse::RelativeContext_try_new(input)
298    }
299
300    /// Return the components of this [`RelativeContext`].
301    ///
302    /// ```
303    /// use wolfram_expr::symbol::RelativeContext;
304    ///
305    /// let context = RelativeContext::try_new("`Sub`Module`").unwrap();
306    ///
307    /// let components = context.components();
308    ///
309    /// assert!(components.len() == 2);
310    /// assert!(components[0].as_str() == "Sub");
311    /// assert!(components[1].as_str() == "Module");
312    /// ```
313    pub fn components(&self) -> Vec<SymbolNameRef> {
314        let RelativeContext(string) = self;
315
316        let comps: Vec<SymbolNameRef> = string
317            .split('`')
318            // Remove the last component, which will always be the empty string
319            .filter(|comp| !comp.is_empty())
320            .map(|comp| {
321                SymbolNameRef::try_new(comp)
322                    .expect("RelativeContext::components(): invalid context component")
323            })
324            .collect();
325
326        comps
327    }
328}
329
330macro_rules! common_impls {
331    (impl $ty:ident) => {
332        impl Display for $ty {
333            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334                let $ty(string) = self;
335
336                write!(f, "{}", string)
337            }
338        }
339
340        impl $ty {
341            /// Get the underlying `&str` representation of this type.
342            pub fn as_str(&self) -> &str {
343                let $ty(string) = self;
344
345                string.as_str()
346            }
347
348            /// Create a new instance of this type from a string, without validating the
349            /// string contents.
350            ///
351            /// It's up to the caller to ensure that the passed `input` has the correct
352            /// syntax.
353            ///
354            /// ## Safety
355            ///
356            /// This function actually does not do anything that would be rejected by
357            /// rustc were the function not marked `unsafe`. However, this function is so
358            /// often *not* what is really needed, it's marked unsafe as a deterent to
359            /// possible users.
360            pub(crate) unsafe fn unchecked_new<S: Into<String>>(input: S) -> $ty {
361                let inner: Arc<String> = Arc::new(input.into());
362                $ty(inner)
363            }
364        }
365    };
366}
367
368common_impls!(impl Symbol);
369common_impls!(impl SymbolName);
370common_impls!(impl Context);
371common_impls!(impl RelativeContext);
372
373//==========================================================
374// Impls -- Borrowed Types
375//==========================================================
376
377impl<'s> SymbolRef<'s> {
378    /// Attempt to parse `string` as an absolute symbol.
379    ///
380    /// # Examples
381    ///
382    /// ```
383    /// use wolfram_expr::symbol::SymbolRef;
384    ///
385    /// assert!(matches!(SymbolRef::try_new("System`List"), Some(_)));
386    /// assert!(matches!(SymbolRef::try_new("List"), None));
387    /// assert!(matches!(SymbolRef::try_new("123"), None));
388    /// ```
389    pub fn try_new(string: &'s str) -> Option<Self> {
390        crate::symbol::parse::SymbolRef_try_new(string)
391    }
392
393    /// Get the borrowed string data.
394    pub fn as_str(&self) -> &'s str {
395        let SymbolRef(string) = self;
396        string
397    }
398
399    /// Convert this borrowed string into an owned [`Symbol`].
400    pub fn to_symbol(&self) -> Symbol {
401        let SymbolRef(string) = self;
402        unsafe { Symbol::unchecked_new(string.to_owned()) }
403    }
404
405    // TODO: Document this method
406    #[doc(hidden)]
407    pub const unsafe fn unchecked_new(string: &'s str) -> Self {
408        SymbolRef(string)
409    }
410
411    /// Get the context path part of a symbol as an [`ContextRef`].
412    pub fn context(&self) -> ContextRef<'s> {
413        let string = self.as_str();
414
415        let last_grave = string
416            .rfind('`')
417            .expect("Failed to find grave '`' character in symbol");
418
419        // SAFETY: All valid Symbol's will contain at least one grave mark '`', will
420        //         have at least 1 character after that grave mark, and the string up
421        //         to and including the last grave mark will be a valid absolute context.
422        let (context, _) = string.split_at(last_grave + 1);
423
424        unsafe { ContextRef::unchecked_new(context) }
425    }
426
427    /// Get the symbol name part of a symbol as a [`SymbolNameRef`].
428    pub fn symbol_name(&self) -> SymbolNameRef<'s> {
429        let string = self.as_str();
430
431        let last_grave = string
432            .rfind('`')
433            .expect("Failed to find grave '`' character in symbol");
434
435        // SAFETY: All valid Symbol's will contain at least one grave mark '`', will
436        //         have at least 1 character after that grave mark, and the string up
437        //         to and including the last grave mark will be a valid absolute context.
438        let (_, name) = string.split_at(last_grave + 1);
439        unsafe { SymbolNameRef::unchecked_new(name) }
440    }
441}
442
443impl<'s> SymbolNameRef<'s> {
444    /// Attempt to parse `string` as a symbol name.
445    pub fn try_new(string: &'s str) -> Option<Self> {
446        crate::symbol::parse::SymbolNameRef_try_new(string)
447    }
448
449    /// Get the borrowed string data.
450    pub fn as_str(&self) -> &'s str {
451        let SymbolNameRef(string) = self;
452        string
453    }
454
455    /// Convert this borrowed string into an owned [`SymbolName`].
456    pub fn to_symbol_name(&self) -> SymbolName {
457        let SymbolNameRef(string) = self;
458        unsafe { SymbolName::unchecked_new(string.to_owned()) }
459    }
460
461    #[doc(hidden)]
462    pub unsafe fn unchecked_new(string: &'s str) -> Self {
463        SymbolNameRef(string)
464    }
465}
466
467impl<'s> ContextRef<'s> {
468    /// Attempt to parse `string` as a context.
469    pub fn try_new(string: &'s str) -> Option<Self> {
470        crate::symbol::parse::ContextRef_try_new(string)
471    }
472
473    /// Get the borrowed string data.
474    pub fn as_str(&self) -> &'s str {
475        let ContextRef(string) = self;
476        string
477    }
478
479    /// Convert this borrowed string into an owned [`Context`].
480    pub fn to_context(&self) -> Context {
481        let ContextRef(string) = self;
482        unsafe { Context::unchecked_new(string.to_owned()) }
483    }
484
485    #[doc(hidden)]
486    pub unsafe fn unchecked_new(string: &'s str) -> Self {
487        ContextRef(string)
488    }
489}
490
491//======================================
492// Formatting impls
493//======================================
494
495impl Display for SymbolNameRef<'_> {
496    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
497        write!(f, "{}", self.as_str())
498    }
499}