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