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}