├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── LICENSE-MIT ├── docs ├── CONTRIBUTING.md └── CHANGELOG.md ├── src ├── tests.rs ├── ptr_cmp.rs ├── symbol │ └── parse.rs ├── conversion.rs ├── lib.rs └── symbol.rs ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .idea 3 | .vscode 4 | /Cargo.lock 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 90 2 | comment_width = 90 3 | match_block_trailing_comma = true 4 | blank_lines_upper_bound = 2 5 | merge_derives = false 6 | overflow_delimited_expr = true 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wolfram-expr" 3 | version = "0.1.4" 4 | authors = ["Connor Gray "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/WolframResearch/wolfram-expr-rs" 9 | description = "Efficient and ergonomic representation of Wolfram expressions in Rust" 10 | keywords = ["wolfram", "wolfram-language", "mathematica", "wolfram-engine", "expression"] 11 | categories = ["encoding"] 12 | 13 | [features] 14 | default = [] 15 | 16 | # Whether to publically export nom functions for parsing symbols. This feature should not 17 | # be considered stable -- it is included only so that wl-parse can build higher-level 18 | # expression parsing on top of it. 19 | unstable_parse = [] 20 | 21 | [dependencies] 22 | ordered-float = "3.4.0" 23 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Wolfram Research Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Wolfram® 2 | 3 | Thank you for taking the time to contribute to the 4 | [Wolfram Research](https://github.com/wolframresearch) repositories on GitHub. 5 | 6 | ## Licensing of Contributions 7 | 8 | By contributing to Wolfram, you agree and affirm that: 9 | 10 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT) 11 | > AND the [Apache 2.0 license](https://opensource.org/licenses/Apache-2.0); and 12 | 13 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later. 14 | 15 | Please see [LICENSE](LICENSE) for licensing conditions pertaining 16 | to individual repositories. 17 | 18 | 19 | ## Bug reports 20 | 21 | ### Security Bugs 22 | 23 | Please **DO NOT** file a public issue regarding a security issue. 24 | Rather, send your report privately to security@wolfram.com. Security 25 | reports are appreciated and we will credit you for it. We do not offer 26 | a security bounty, but the forecast in your neighborhood will be cloudy 27 | with a chance of Wolfram schwag! 28 | 29 | ### General Bugs 30 | 31 | Please use the repository issues page to submit general bug issues. 32 | 33 | Please do not duplicate issues. 34 | 35 | Please do send a complete and well-written report to us. Note: **the 36 | thoroughness of your report will positively correlate with our ability to address it**. 37 | 38 | When reporting issues, always include: 39 | 40 | * Your version of *Mathematica*® or the Wolfram Language. 41 | * Your operating system. 42 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::symbol::{ContextRef, RelativeContext, SymbolNameRef, SymbolRef}; 2 | 3 | /// `(input, is Symbol, is SymbolName, is Context, is RelativeContext)` 4 | #[rustfmt::skip] 5 | const DATA: &[(&str, bool, bool, bool, bool)] = &[ 6 | // Symbol-like 7 | ("foo`bar", true , false, false, false), 8 | ("foo`bar`baz", true , false, false, false), 9 | ("foo`bar5", true , false, false, false), 10 | ("foo`5bar", false, false, false, false), 11 | ("5foo`bar", false, false, false, false), 12 | ("foo``bar", false, false, false, false), 13 | ("foo`$bar", true , false, false, false), 14 | ("$foo`$bar", true , false, false, false), 15 | ("$foo`$$$", true , false, false, false), 16 | ("$$$`$$$", true , false, false, false), 17 | 18 | // SymbolName-like 19 | ("foo", false, true, false, false), 20 | ("foo5", false, true, false, false), 21 | ("foo5bar", false, true, false, false), 22 | ("$foo", false, true, false, false), 23 | ("5foo", false, false, false, false), 24 | ("foo_bar", false, false, false, false), 25 | ("_foo", false, false, false, false), 26 | 27 | // TODO: RelativeSymbol-like 28 | ("`foo", false, false, false, false), 29 | ("`foo`bar", false, false, false, false), 30 | 31 | // Context-like 32 | ("foo`", false, false, true, false), 33 | ("foo`bar`", false, false, true, false), 34 | 35 | // RelativeContext-like 36 | ("`foo`", false, false, false, true), 37 | ("`foo`bar`", false, false, false, true), 38 | ]; 39 | 40 | #[test] 41 | pub fn test_symbol_like_parsing() { 42 | for (input, is_symbol, is_symbol_name, is_context, is_rel_context) in 43 | DATA.iter().copied() 44 | { 45 | println!("input: {input}"); 46 | assert_eq!(SymbolRef::try_new(input).is_some(), is_symbol); 47 | assert_eq!(SymbolNameRef::try_new(input).is_some(), is_symbol_name); 48 | assert_eq!(ContextRef::try_new(input).is_some(), is_context); 49 | assert_eq!(RelativeContext::try_new(input).is_some(), is_rel_context); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ptr_cmp.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | hash::{Hash, Hasher}, 3 | sync::Arc, 4 | }; 5 | 6 | use crate::{Expr, ExprKind}; 7 | 8 | 9 | /// [`Expr`] wrapper that compares by reference instead of by value. 10 | /// 11 | /// The standard [`Expr`] type uses *value* semantics for comparision: two [`Expr`] 12 | /// instances are considered equal iff the Wolfram expression values they contain 13 | /// are the same. 14 | /// 15 | /// [`ExprRefCmp`] uses *reference* semantics for comparision: two [`ExprRefCmp`] 16 | /// instances are considered equal iff they are pointers to the same 17 | /// reference-counted expression allocation. 18 | /// 19 | /// The [`Hash`] and [`PartialEq`] implementations for this type use the pointer 20 | /// address of the reference counted expression data. 21 | /// 22 | /// # Motivation 23 | /// 24 | /// Two [`Expr`] instances will compare equal to each other if their values are 25 | /// semantically the same. That means that even if the two [`Expr`] are 26 | /// different allocations, they are still considered to be the same expression. 27 | /// 28 | /// However, in some cases it is useful to distinguish [`Expr`] instances that 29 | /// may contain semantically identical Wolfram expressions, but that are 30 | /// different allocations. 31 | /// 32 | /// For example, this type is used in `wl_parse::source_map` to give unique 33 | /// source mappings, so that [`Expr`]s that are equal according to the 34 | /// `PartialEq` impl for [`ExprKind`] (and whose hash values are therefore the 35 | /// same) can be differentiated. 36 | #[derive(Debug)] 37 | pub struct ExprRefCmp(pub Expr); 38 | 39 | impl Hash for ExprRefCmp { 40 | fn hash(&self, state: &mut H) { 41 | let ExprRefCmp(Expr { inner }) = self; 42 | let ptr: *const ExprKind = Arc::as_ptr(inner); 43 | ptr.hash(state); 44 | } 45 | } 46 | 47 | impl PartialEq for ExprRefCmp { 48 | fn eq(&self, other: &Self) -> bool { 49 | let ExprRefCmp(Expr { inner: self_inner }) = self; 50 | let ExprRefCmp(Expr { inner: other_inner }) = other; 51 | 52 | Arc::ptr_eq(self_inner, other_inner) 53 | } 54 | } 55 | 56 | impl Eq for ExprRefCmp {} 57 | 58 | // TODO: Add tests that `ExprRefCmp` is working as expected 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wolfram-expr 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/wolfram-expr.svg)](https://crates.io/crates/wolfram-expr) 4 | ![License](https://img.shields.io/crates/l/wolfram-expr.svg) 5 | [![Documentation](https://docs.rs/wolfram-expr/badge.svg)](https://docs.rs/wolfram-expr) 6 | 7 |

8 | API Documentation 9 | | 10 | Changelog 11 | | 12 | Contributing 13 |

14 | 15 | Efficient and ergonomic representation of Wolfram expressions in Rust. 16 | 17 | ## Examples 18 | 19 | Construct the expression `{1, 2, 3}`: 20 | 21 | ```rust 22 | use wolfram_expr::{Expr, Symbol}; 23 | 24 | let expr = Expr::normal(Symbol::new("System`List"), vec![ 25 | Expr::from(1), 26 | Expr::from(2), 27 | Expr::from(3) 28 | ]); 29 | ``` 30 | 31 | Pattern match over different expression variants: 32 | 33 | ```rust 34 | use wolfram_expr::{Expr, ExprKind}; 35 | 36 | let expr = Expr::from("some arbitrary expression"); 37 | 38 | match expr.kind() { 39 | ExprKind::Integer(1) => println!("got 1"), 40 | ExprKind::Integer(n) => println!("got {}", n), 41 | ExprKind::Real(_) => println!("got a real number"), 42 | ExprKind::String(s) => println!("got string: {}", s), 43 | ExprKind::Symbol(sym) => println!("got symbol named {}", sym.symbol_name()), 44 | ExprKind::Normal(e) => println!( 45 | "got expr with head {} and length {}", 46 | e.head(), 47 | e.elements().len() 48 | ), 49 | } 50 | ``` 51 | 52 | ## Related Links 53 | 54 | #### Related crates 55 | 56 | * [`wolfram-library-link`][wolfram-library-link] — author libraries that can be 57 | dynamically loaded by the Wolfram Language. 58 | * [`wstp`][wstp] — bindings to the Wolfram Symbolic Transport Protocol, used for passing 59 | arbitrary Wolfram expressions between programs. 60 | * [`wolfram-app-discovery`][wolfram-app-discovery] — utility for locating local 61 | installations of Wolfram applications and the Wolfram Language. 62 | 63 | 64 | [wstp]: https://github.com/WolframResearch/wstp-rs 65 | [wolfram-app-discovery]: https://crates.io/crates/wolfram-app-discovery 66 | [wolfram-library-link]: https://github.com/WolframResearch/wolfram-library-link-rs 67 | 68 | ## License 69 | 70 | Licensed under either of 71 | 72 | * Apache License, Version 2.0 73 | ([LICENSE-APACHE](LICENSE-APACHE) or ) 74 | * MIT license 75 | ([LICENSE-MIT](LICENSE-MIT) or ) 76 | 77 | at your option. 78 | 79 | ## Contribution 80 | 81 | Unless you explicitly state otherwise, any contribution intentionally submitted 82 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 83 | dual licensed as above, without any additional terms or conditions. 84 | 85 | See [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for more information. 86 | -------------------------------------------------------------------------------- /src/symbol/parse.rs: -------------------------------------------------------------------------------- 1 | // TODO(!): Replace all of this symbol parsing logic with functionality from 2 | // wolfram-code-parse, once that is available. 3 | 4 | use crate::symbol::{ContextRef, RelativeContext, SymbolNameRef, SymbolRef}; 5 | 6 | #[allow(non_snake_case)] 7 | pub(super) fn SymbolRef_try_new<'s>(string: &'s str) -> Option> { 8 | if parse_symbol_like(string)? == SymbolLike::AbsoluteSymbol { 9 | Some(SymbolRef(string)) 10 | } else { 11 | None 12 | } 13 | } 14 | 15 | #[allow(non_snake_case)] 16 | pub(super) fn SymbolNameRef_try_new<'s>(string: &'s str) -> Option> { 17 | if parse_symbol_like(string)? == SymbolLike::SymbolName { 18 | Some(SymbolNameRef(string)) 19 | } else { 20 | None 21 | } 22 | } 23 | 24 | #[allow(non_snake_case)] 25 | pub(super) fn ContextRef_try_new<'s>(string: &'s str) -> Option> { 26 | if parse_symbol_like(string)? == SymbolLike::AbsoluteContext { 27 | Some(ContextRef(string)) 28 | } else { 29 | None 30 | } 31 | } 32 | 33 | // TODO(cleanup): Add RelativeContextRef type, use here instead. 34 | #[allow(non_snake_case)] 35 | pub(super) fn RelativeContext_try_new(input: &str) -> Option { 36 | if parse_symbol_like(input)? == SymbolLike::RelativeContext { 37 | Some(unsafe { RelativeContext::unchecked_new(input.to_owned()) }) 38 | } else { 39 | None 40 | } 41 | } 42 | 43 | #[derive(Debug, PartialEq)] 44 | enum SymbolLike { 45 | /// `` ctx`foo `` 46 | AbsoluteSymbol, 47 | /// `foo` 48 | SymbolName, 49 | /// `` `foo `` 50 | RelativeSymbol, 51 | /// `` ctx` `` 52 | AbsoluteContext, 53 | /// `` `ctx` `` 54 | RelativeContext, 55 | } 56 | 57 | fn parse_symbol_like(input: &str) -> Option { 58 | if input.is_empty() { 59 | return None; 60 | } 61 | 62 | let components: Vec<&str> = input.split("`").collect(); 63 | 64 | let like = match components.as_slice() { 65 | [only] if is_symbol_component(*only) => SymbolLike::SymbolName, 66 | // "`...`" 67 | ["", inner @ .., ""] if inner.iter().copied().all(is_symbol_component) => { 68 | SymbolLike::RelativeContext 69 | }, 70 | // "`..." 71 | ["", rest @ ..] if rest.iter().copied().all(is_symbol_component) => { 72 | SymbolLike::RelativeSymbol 73 | }, 74 | // "...`" 75 | [most @ .., ""] if most.iter().copied().all(is_symbol_component) => { 76 | SymbolLike::AbsoluteContext 77 | }, 78 | 79 | components if components.iter().copied().all(is_symbol_component) => { 80 | SymbolLike::AbsoluteSymbol 81 | }, 82 | 83 | _ => return None, 84 | }; 85 | 86 | Some(like) 87 | } 88 | 89 | 90 | fn is_symbol_component(str: &str) -> bool { 91 | if str.is_empty() { 92 | return false; 93 | } 94 | 95 | debug_assert!(!str.contains('`')); 96 | 97 | let mut chars = str.chars(); 98 | 99 | let first_char = chars.next().unwrap(); 100 | 101 | if !first_char.is_alphabetic() && first_char != '$' { 102 | return false; 103 | } 104 | 105 | for char in chars { 106 | match char { 107 | '_' | '-' => return false, 108 | _ if char.is_alphabetic() => (), 109 | _ if char.is_digit(10) => (), 110 | '$' => (), 111 | _ => return false, 112 | } 113 | } 114 | 115 | true 116 | } 117 | 118 | #[test] 119 | fn test_is_symbol_component() { 120 | assert!(is_symbol_component("foo")); 121 | assert!(is_symbol_component("$bar")); 122 | } 123 | 124 | #[test] 125 | fn test_parse_symbol_like() { 126 | assert_eq!(parse_symbol_like("foo"), Some(SymbolLike::SymbolName)); 127 | } 128 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | 11 | 12 | ## [0.1.4] – 2023-02-03 13 | 14 | ### Changed 15 | 16 | * Remove `nom` and `nom_locate` as dependencies of `wolfram-expr`. ([#17]) 17 | 18 | * Mark `SymbolRef::unchecked_new()` as `const`. ([#17]) 19 | 20 | * Update `ordered-float` dependency from v1.x.x series to v3.4.0. ([#17]) 21 | 22 | 23 | 24 | ## [0.1.3] – 2022-12-06 25 | 26 | ### Added 27 | 28 | * Added new convenience methods for working with `SymbolStr`s. ([#15]) 29 | 30 | The following methods have been added: 31 | 32 | * [`Symbol::as_symbol_ref()`](https://docs.rs/wolfram-expr/0.1.3/wolfram_expr/struct.Symbol.html#method.as_symbol_ref) 33 | * [`SymbolRef::context()`](https://docs.rs/wolfram-expr/0.1.3/wolfram_expr/symbol/struct.SymbolRef.html#method.context) 34 | * [`SymbolRef::symbol_name()`](https://docs.rs/wolfram-expr/0.1.3/wolfram_expr/symbol/struct.SymbolRef.html#method.symbol_name) 35 | 36 | 37 | 38 | ## [0.1.2] – 2022-07-25 39 | 40 | ### Added 41 | 42 | * Added new convenience methods for constructing and converting `Expr`s. ([#7]) 43 | 44 | The following operations are new: 45 | 46 | * [`Expr::try_as_bool`](https://docs.rs/wolfram-expr/0.1.2/wolfram_expr/struct.Expr.html#method.try_as_bool) 47 | * [`Expr::try_as_str`](https://docs.rs/wolfram-expr/0.1.2/wolfram_expr/struct.Expr.html#method.try_as_str) 48 | * [`Expr::rule_delayed`](https://docs.rs/wolfram-expr/0.1.2/wolfram_expr/struct.Expr.html#method.rule_delayed) 49 | * `impl From for Expr` 50 | 51 | The following methods were renamed, and the previous method marked 52 | `#[deprecated(..)]`: 53 | 54 | * [`Expr::try_as_normal`](https://docs.rs/wolfram-expr/0.1.2/wolfram_expr/struct.Expr.html#method.try_as_normal) 55 | (was `Expr::try_normal`) 56 | * [`Expr::try_as_symbol`](https://docs.rs/wolfram-expr/0.1.2/wolfram_expr/struct.Expr.html#method.try_as_symbol) 57 | (was `Expr::try_symbol`) 58 | * [`Expr::try_as_number`](https://docs.rs/wolfram-expr/0.1.2/wolfram_expr/struct.Expr.html#method.try_as_number) 59 | (was `Expr::try_number`) 60 | 61 | * Add unstable [`ExprRefCmp`] type behind the `"unstable_parse"` feature flag. ([#12]) 62 | 63 | 64 | 65 | ## [0.1.1] – 2022-02-18 66 | 67 | ### Added 68 | 69 | * Added [`Expr::rule()`](https://docs.rs/wolfram-expr/0.1.1/wolfram_expr/struct.Expr.html#method.rule) 70 | and [`Expr::list()`](https://docs.rs/wolfram-expr/0.1.1/wolfram_expr/struct.Expr.html#method.list) 71 | methods for more convenient construction of `Rule` and `List` expressions. ([#5]) 72 | 73 | Construct the expression `FontFamily -> "Courier New"`: 74 | 75 | ```rust 76 | use wolfram_expr::{Expr, Symbol}; 77 | 78 | let option = Expr::rule(Symbol::new("System`FontFamily"), Expr::string("Courier New")); 79 | ``` 80 | 81 | Construct the expression `{1, 2, 3}`: 82 | 83 | ```rust 84 | use wolfram_expr::Expr; 85 | 86 | let list = Expr::list(vec![Expr::from(1), Expr::from(2), Expr::from(3)]); 87 | ``` 88 | 89 | 90 | 91 | ## [0.1.0] – 2022-02-08 92 | 93 | ### Added 94 | 95 | * The [`Expr`](https://docs.rs/wolfram-expr/0.1.0/wolfram_expr/struct.Expr.html) type, for 96 | representing Wolfram Language expressions in an efficient and easy-to-process structure. 97 | 98 | Construct the expression `{1, 2, 3}`: 99 | 100 | ```rust 101 | use wolfram_expr::{Expr, Symbol}; 102 | 103 | let expr = Expr::normal(Symbol::new("System`List"), vec![ 104 | Expr::from(1), 105 | Expr::from(2), 106 | Expr::from(3) 107 | ]); 108 | ``` 109 | 110 | Pattern match over different expression variants: 111 | 112 | ```rust 113 | use wolfram_expr::{Expr, ExprKind}; 114 | 115 | let expr = Expr::from("some arbitrary expression"); 116 | 117 | match expr.kind() { 118 | ExprKind::Integer(1) => println!("got 1"), 119 | ExprKind::Integer(n) => println!("got {}", n), 120 | ExprKind::Real(_) => println!("got a real number"), 121 | ExprKind::String(s) => println!("got string: {}", s), 122 | ExprKind::Symbol(sym) => println!("got symbol named {}", sym.symbol_name()), 123 | ExprKind::Normal(e) => println!( 124 | "got expr with head {} and length {}", 125 | e.head(), 126 | e.elements().len() 127 | ), 128 | } 129 | ``` 130 | 131 | 132 | 133 | 134 | [#5]: https://github.com/WolframResearch/wolfram-expr-rs/pull/5 135 | 136 | 137 | [#7]: https://github.com/WolframResearch/wolfram-expr-rs/pull/7 138 | [#12]: https://github.com/WolframResearch/wolfram-expr-rs/pull/12 139 | 140 | 141 | [#15]: https://github.com/WolframResearch/wolfram-expr-rs/pull/15 142 | 143 | 144 | [#17]: https://github.com/WolframResearch/wolfram-expr-rs/pull/17 145 | 146 | 147 | 148 | [Unreleased]: https://github.com/WolframResearch/wolfram-expr-rs/compare/v0.1.4...HEAD 149 | 150 | [0.1.4]: https://github.com/WolframResearch/wolfram-expr-rs/compare/v0.1.3...v0.1.4 151 | [0.1.3]: https://github.com/WolframResearch/wolfram-expr-rs/compare/v0.1.2...v0.1.3 152 | [0.1.2]: https://github.com/WolframResearch/wolfram-expr-rs/compare/v0.1.1...v0.1.2 153 | [0.1.1]: https://github.com/WolframResearch/wolfram-expr-rs/compare/v0.1.0...v0.1.1 154 | [0.1.0]: https://github.com/WolframResearch/wolfram-expr-rs/releases/tag/v0.1.0 -------------------------------------------------------------------------------- /src/conversion.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | 4 | impl Expr { 5 | /// If this is a [`Normal`] expression, return that. Otherwise return None. 6 | pub fn try_as_normal(&self) -> Option<&Normal> { 7 | match self.kind() { 8 | ExprKind::Normal(ref normal) => Some(normal), 9 | ExprKind::Symbol(_) 10 | | ExprKind::String(_) 11 | | ExprKind::Integer(_) 12 | | ExprKind::Real(_) => None, 13 | } 14 | } 15 | 16 | /// If this is a [`True`](http://reference.wolfram.com/language/ref/True.html) 17 | /// or [`False`](http://reference.wolfram.com/language/ref/False.html) symbol, 18 | /// return that. Otherwise return None. 19 | pub fn try_as_bool(&self) -> Option { 20 | let s = self.try_as_symbol()?; 21 | if s.as_str() == "System`True" { 22 | return Some(true); 23 | } 24 | if s.as_str() == "System`False" { 25 | return Some(false); 26 | } 27 | None 28 | } 29 | 30 | /// If this is a [`ExprKind::String`] expression, return that. Otherwise return None. 31 | pub fn try_as_str(&self) -> Option<&str> { 32 | match self.kind() { 33 | ExprKind::String(ref string) => Some(string.as_str()), 34 | _ => None, 35 | } 36 | } 37 | 38 | /// If this is a [`Symbol`] expression, return that. Otherwise return None. 39 | pub fn try_as_symbol(&self) -> Option<&Symbol> { 40 | match self.kind() { 41 | ExprKind::Symbol(ref symbol) => Some(symbol), 42 | ExprKind::Normal(_) 43 | | ExprKind::String(_) 44 | | ExprKind::Integer(_) 45 | | ExprKind::Real(_) => None, 46 | } 47 | } 48 | 49 | /// If this is a [`Number`] expression, return that. Otherwise return None. 50 | pub fn try_as_number(&self) -> Option { 51 | match self.kind() { 52 | ExprKind::Integer(int) => Some(Number::Integer(*int)), 53 | ExprKind::Real(real) => Some(Number::Real(*real)), 54 | ExprKind::Normal(_) | ExprKind::String(_) | ExprKind::Symbol(_) => None, 55 | } 56 | } 57 | 58 | //--------------------------------------------------------------------------- 59 | // SEMVER: These methods have been replaced; remove them in a future version. 60 | //--------------------------------------------------------------------------- 61 | 62 | #[deprecated(note = "Use Expr::try_as_normal() instead")] 63 | #[allow(missing_docs)] 64 | pub fn try_normal(&self) -> Option<&Normal> { 65 | self.try_as_normal() 66 | } 67 | 68 | #[deprecated(note = "Use Expr::try_as_symbol() instead")] 69 | #[allow(missing_docs)] 70 | pub fn try_symbol(&self) -> Option<&Symbol> { 71 | self.try_as_symbol() 72 | } 73 | 74 | #[deprecated(note = "Use Expr::try_as_number() instead")] 75 | #[allow(missing_docs)] 76 | pub fn try_number(&self) -> Option { 77 | self.try_as_number() 78 | } 79 | } 80 | 81 | //======================================= 82 | // Conversion trait impl's 83 | //======================================= 84 | 85 | impl From for Expr { 86 | fn from(sym: Symbol) -> Expr { 87 | Expr::symbol(sym) 88 | } 89 | } 90 | 91 | impl From<&Symbol> for Expr { 92 | fn from(sym: &Symbol) -> Expr { 93 | Expr::symbol(sym) 94 | } 95 | } 96 | 97 | impl From for Expr { 98 | fn from(normal: Normal) -> Expr { 99 | Expr { 100 | inner: Arc::new(ExprKind::Normal(normal)), 101 | } 102 | } 103 | } 104 | 105 | impl From for Expr { 106 | fn from(value: bool) -> Expr { 107 | match value { 108 | true => Expr::symbol(Symbol::new("System`True")), 109 | false => Expr::symbol(Symbol::new("System`False")), 110 | } 111 | } 112 | } 113 | 114 | macro_rules! string_like { 115 | ($($t:ty),*) => { 116 | $( 117 | impl From<$t> for Expr { 118 | fn from(s: $t) -> Expr { 119 | Expr::string(s) 120 | } 121 | } 122 | )* 123 | } 124 | } 125 | 126 | string_like!(&str, &String, String); 127 | 128 | //-------------------- 129 | // Integer conversions 130 | //-------------------- 131 | 132 | impl From for Expr { 133 | fn from(int: u8) -> Expr { 134 | Expr::from(i64::from(int)) 135 | } 136 | } 137 | 138 | impl From for Expr { 139 | fn from(int: i8) -> Expr { 140 | Expr::from(i64::from(int)) 141 | } 142 | } 143 | 144 | impl From for Expr { 145 | fn from(int: u16) -> Expr { 146 | Expr::from(i64::from(int)) 147 | } 148 | } 149 | 150 | impl From for Expr { 151 | fn from(int: i16) -> Expr { 152 | Expr::from(i64::from(int)) 153 | } 154 | } 155 | 156 | impl From for Expr { 157 | fn from(int: u32) -> Expr { 158 | Expr::from(i64::from(int)) 159 | } 160 | } 161 | 162 | impl From for Expr { 163 | fn from(int: i32) -> Expr { 164 | Expr::from(i64::from(int)) 165 | } 166 | } 167 | 168 | impl From for Expr { 169 | fn from(int: i64) -> Expr { 170 | Expr::number(Number::Integer(int)) 171 | } 172 | } 173 | 174 | // impl From for ExprKind { 175 | // fn from(normal: Normal) -> ExprKind { 176 | // ExprKind::Normal(Box::new(normal)) 177 | // } 178 | // } 179 | 180 | // impl From for ExprKind { 181 | // fn from(symbol: Symbol) -> ExprKind { 182 | // ExprKind::Symbol(symbol) 183 | // } 184 | // } 185 | 186 | impl From for ExprKind { 187 | fn from(number: Number) -> ExprKind { 188 | match number { 189 | Number::Integer(int) => ExprKind::Integer(int), 190 | Number::Real(real) => ExprKind::Real(real), 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Efficient and ergonomic representation of Wolfram expressions in Rust. 2 | 3 | #![allow(clippy::let_and_return)] 4 | #![warn(missing_docs)] 5 | 6 | mod conversion; 7 | mod ptr_cmp; 8 | 9 | pub mod symbol; 10 | 11 | #[cfg(test)] 12 | mod tests; 13 | 14 | #[doc(hidden)] 15 | mod test_readme { 16 | // Ensure that doc tests in the README.md file get run. 17 | #![doc = include_str ! ("../README.md")] 18 | } 19 | 20 | 21 | use std::fmt; 22 | use std::mem; 23 | use std::sync::Arc; 24 | 25 | 26 | #[doc(inline)] 27 | pub use self::symbol::Symbol; 28 | 29 | #[cfg(feature = "unstable_parse")] 30 | pub use self::ptr_cmp::ExprRefCmp; 31 | 32 | /// Wolfram Language expression. 33 | /// 34 | /// # Example 35 | /// 36 | /// Construct the expression `{1, 2, 3}`: 37 | /// 38 | /// ``` 39 | /// use wolfram_expr::{Expr, Symbol}; 40 | /// 41 | /// let expr = Expr::normal(Symbol::new("System`List"), vec![ 42 | /// Expr::from(1), 43 | /// Expr::from(2), 44 | /// Expr::from(3) 45 | /// ]); 46 | /// ``` 47 | /// 48 | /// # Reference counting 49 | /// 50 | /// Internally, `Expr` is an atomically reference-counted [`ExprKind`]. This makes cloning 51 | /// an expression computationally inexpensive. 52 | #[derive(Clone, PartialEq, Eq, Hash)] 53 | pub struct Expr { 54 | inner: Arc, 55 | } 56 | 57 | // Assert that Expr has the same size and alignment as a usize / pointer. 58 | const _: () = assert!(mem::size_of::() == mem::size_of::()); 59 | const _: () = assert!(mem::size_of::() == mem::size_of::<*const ()>()); 60 | const _: () = assert!(mem::align_of::() == mem::align_of::()); 61 | const _: () = assert!(mem::align_of::() == mem::align_of::<*const ()>()); 62 | 63 | impl Expr { 64 | /// Construct a new expression from an [`ExprKind`]. 65 | pub fn new(kind: ExprKind) -> Expr { 66 | Expr { 67 | inner: Arc::new(kind), 68 | } 69 | } 70 | 71 | /// Consume `self` and return an owned [`ExprKind`]. 72 | /// 73 | /// If the reference count of `self` is equal to 1 this function will *not* perform 74 | /// a clone of the stored `ExprKind`, making this operation very cheap in that case. 75 | // Silence the clippy warning about this method. While this method technically doesn't 76 | // follow the Rust style convention of using `into` to prefix methods which take 77 | // `self` by move, I think using `to` is more appropriate given the expected 78 | // performance characteristics of this method. `into` implies that the method is 79 | // always returning data already owned by this type, and as such should be a very 80 | // cheap operation. This method can make no such guarantee; if the reference count is 81 | // 1, then performance is very good, but if the reference count is >1, a deeper clone 82 | // must be done. 83 | #[allow(clippy::wrong_self_convention)] 84 | pub fn to_kind(self) -> ExprKind { 85 | match Arc::try_unwrap(self.inner) { 86 | Ok(kind) => kind, 87 | Err(self_) => (*self_).clone(), 88 | } 89 | } 90 | 91 | /// Get the [`ExprKind`] representing this expression. 92 | pub fn kind(&self) -> &ExprKind { 93 | &*self.inner 94 | } 95 | 96 | /// Get mutable access to the [`ExprKind`] that represents this expression. 97 | /// 98 | /// If the reference count of the underlying shared pointer is not equal to 1, this 99 | /// will clone the [`ExprKind`] to make it unique. 100 | pub fn kind_mut(&mut self) -> &mut ExprKind { 101 | Arc::make_mut(&mut self.inner) 102 | } 103 | 104 | /// Retrieve the reference count of this expression. 105 | pub fn ref_count(&self) -> usize { 106 | Arc::strong_count(&self.inner) 107 | } 108 | 109 | /// Construct a new normal expression from the head and elements. 110 | pub fn normal>(head: H, contents: Vec) -> Expr { 111 | let head = head.into(); 112 | // let contents = contents.into(); 113 | Expr { 114 | inner: Arc::new(ExprKind::Normal(Normal { head, contents })), 115 | } 116 | } 117 | 118 | // TODO: Should Expr's be cached? Especially Symbol exprs? Would certainly save 119 | // a lot of allocations. 120 | /// Construct a new expression from a [`Symbol`]. 121 | pub fn symbol>(s: S) -> Expr { 122 | let s = s.into(); 123 | Expr { 124 | inner: Arc::new(ExprKind::Symbol(s)), 125 | } 126 | } 127 | 128 | /// Construct a new expression from a [`Number`]. 129 | pub fn number(num: Number) -> Expr { 130 | Expr { 131 | inner: Arc::new(ExprKind::from(num)), 132 | } 133 | } 134 | 135 | /// Construct a new expression from a [`String`]. 136 | pub fn string>(s: S) -> Expr { 137 | Expr { 138 | inner: Arc::new(ExprKind::String(s.into())), 139 | } 140 | } 141 | 142 | /// Construct an expression from a floating-point number. 143 | /// 144 | /// ``` 145 | /// # use wolfram_expr::Expr; 146 | /// let expr = Expr::real(3.14159); 147 | /// ``` 148 | /// 149 | /// # Panics 150 | /// 151 | /// This function will panic if `real` is NaN. 152 | pub fn real(real: f64) -> Expr { 153 | Expr::number(Number::real(real)) 154 | } 155 | 156 | /// Returns the outer-most symbol "tag" used in this expression. 157 | /// 158 | /// To illustrate: 159 | /// 160 | /// Expression | Tag 161 | /// -------------|---- 162 | /// `5` | `None` 163 | /// `"hello"` | `None` 164 | /// `foo` | `foo` 165 | /// `f[1, 2, 3]` | `f` 166 | /// `g[x][y]` | `g` 167 | // 168 | // TODO: _[x] probably should return None, even though technically 169 | // Blank[][x] has the tag Blank. 170 | // TODO: The above TODO is probably wrong -- tag() shouldn't have any language 171 | // semantics built in to it. 172 | pub fn tag(&self) -> Option { 173 | match *self.inner { 174 | ExprKind::Integer(_) | ExprKind::Real(_) | ExprKind::String(_) => None, 175 | ExprKind::Normal(ref normal) => normal.head.tag(), 176 | ExprKind::Symbol(ref sym) => Some(sym.clone()), 177 | } 178 | } 179 | 180 | /// If this represents a [`Normal`] expression, return its head. Otherwise, return 181 | /// `None`. 182 | pub fn normal_head(&self) -> Option { 183 | match *self.inner { 184 | ExprKind::Normal(ref normal) => Some(normal.head.clone()), 185 | ExprKind::Symbol(_) 186 | | ExprKind::Integer(_) 187 | | ExprKind::Real(_) 188 | | ExprKind::String(_) => None, 189 | } 190 | } 191 | 192 | /// Attempt to get the element at `index` of a `Normal` expression. 193 | /// 194 | /// Return `None` if this is not a `Normal` expression, or the given index is out of 195 | /// bounds. 196 | /// 197 | /// `index` is 0-based. The 0th index is the first element, not the head. 198 | /// 199 | /// This function does not panic. 200 | pub fn normal_part(&self, index_0: usize) -> Option<&Expr> { 201 | match self.kind() { 202 | ExprKind::Normal(ref normal) => normal.contents.get(index_0), 203 | ExprKind::Symbol(_) 204 | | ExprKind::Integer(_) 205 | | ExprKind::Real(_) 206 | | ExprKind::String(_) => None, 207 | } 208 | } 209 | 210 | /// Returns `true` if `self` is a `Normal` expr with the head `sym`. 211 | pub fn has_normal_head(&self, sym: &Symbol) -> bool { 212 | match *self.kind() { 213 | ExprKind::Normal(ref normal) => normal.has_head(sym), 214 | _ => false, 215 | } 216 | } 217 | 218 | //================================== 219 | // Common values 220 | //================================== 221 | 222 | /// [`Null`](https://reference.wolfram.com/language/ref/Null.html) WL. 223 | pub fn null() -> Expr { 224 | Expr::symbol(unsafe { Symbol::unchecked_new("System`Null") }) 225 | } 226 | 227 | //================================== 228 | // Convenience creation functions 229 | //================================== 230 | 231 | /// Construct a new `Rule[_, _]` expression from the left-hand side and right-hand 232 | /// side. 233 | /// 234 | /// # Example 235 | /// 236 | /// Construct the expression `FontSize -> 16`: 237 | /// 238 | /// ``` 239 | /// use wolfram_expr::{Expr, Symbol}; 240 | /// 241 | /// let option = Expr::rule(Symbol::new("System`FontSize"), Expr::from(16)); 242 | /// ``` 243 | pub fn rule>(lhs: LHS, rhs: Expr) -> Expr { 244 | let lhs = lhs.into(); 245 | 246 | Expr::normal(Symbol::new("System`Rule"), vec![lhs, rhs]) 247 | } 248 | /// Construct a new `RuleDelayed[_, _]` expression from the left-hand side and right-hand 249 | /// side. 250 | /// 251 | /// # Example 252 | /// 253 | /// Construct the expression `x :> RandomReal[]`: 254 | /// 255 | /// ``` 256 | /// use wolfram_expr::{Expr, Symbol}; 257 | /// 258 | /// let delayed = Expr::rule_delayed( 259 | /// Symbol::new("Global`x"), 260 | /// Expr::normal(Symbol::new("System`RandomReal"), vec![]) 261 | /// ); 262 | /// ``` 263 | pub fn rule_delayed>(lhs: LHS, rhs: Expr) -> Expr { 264 | let lhs = lhs.into(); 265 | 266 | Expr::normal(Symbol::new("System`RuleDelayed"), vec![lhs, rhs]) 267 | } 268 | 269 | /// Construct a new `List[...]`(`{...}`) expression from it's elements. 270 | /// 271 | /// # Example 272 | /// 273 | /// Construct the expression `{1, 2, 3}`: 274 | /// 275 | /// ``` 276 | /// use wolfram_expr::Expr; 277 | /// 278 | /// let list = Expr::list(vec![Expr::from(1), Expr::from(2), Expr::from(3)]); 279 | /// ``` 280 | pub fn list(elements: Vec) -> Expr { 281 | Expr::normal(Symbol::new("System`List"), elements) 282 | } 283 | } 284 | 285 | /// Wolfram Language expression variants. 286 | #[allow(missing_docs)] 287 | #[derive(Clone, PartialEq, Eq, Hash)] 288 | pub enum ExprKind { 289 | Integer(i64), 290 | Real(F64), 291 | String(String), 292 | Symbol(Symbol), 293 | Normal(Normal), 294 | } 295 | 296 | /// Wolfram Language "normal" expression: `f[...]`. 297 | /// 298 | /// A *normal* expression is any expression that consists of a head and zero or 299 | /// more arguments. 300 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 301 | pub struct Normal { 302 | /// The head of this normal expression. 303 | head: E, 304 | 305 | /// The elements of this normal expression. 306 | /// 307 | /// If `head` conceptually represents a function, these are the arguments that are 308 | /// being applied to `head`. 309 | contents: Vec, 310 | } 311 | 312 | /// Subset of [`ExprKind`] that covers number-type expression values. 313 | #[allow(missing_docs)] 314 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)] 315 | pub enum Number { 316 | // TODO: Rename this to MachineInteger 317 | Integer(i64), 318 | // TODO: Make an explicit MachineReal type which hides the inner f64, so that other 319 | // code can make use of WL machine reals with a guaranteed type. In 320 | // particular, change wl_compile::mir::Constant to use that type. 321 | Real(F64), 322 | } 323 | 324 | /// 64-bit floating-point real number. Not NaN. 325 | pub type F64 = ordered_float::NotNan; 326 | /// 32-bit floating-point real number. Not NaN. 327 | pub type F32 = ordered_float::NotNan; 328 | 329 | //======================================= 330 | // Type Impl's 331 | //======================================= 332 | 333 | impl Normal { 334 | /// Construct a new normal expression from the head and elements. 335 | pub fn new>(head: E, contents: Vec) -> Self { 336 | Normal { 337 | head: head.into(), 338 | contents, 339 | } 340 | } 341 | 342 | /// The head of this normal expression. 343 | pub fn head(&self) -> &Expr { 344 | &self.head 345 | } 346 | 347 | /// The elements of this normal expression. 348 | /// 349 | /// If `head` conceptually represents a function, these are the arguments that are 350 | /// being applied to `head`. 351 | pub fn elements(&self) -> &[Expr] { 352 | &self.contents 353 | } 354 | 355 | /// The elements of this normal expression. 356 | /// 357 | /// Use [`Normal::elements()`] to get a reference to this value. 358 | pub fn into_elements(self) -> Vec { 359 | self.contents 360 | } 361 | 362 | /// Returns `true` if the head of this expression is `sym`. 363 | pub fn has_head(&self, sym: &Symbol) -> bool { 364 | self.head == *sym 365 | } 366 | } 367 | 368 | impl Number { 369 | /// # Panics 370 | /// 371 | /// This function will panic if `r` is NaN. 372 | /// 373 | /// TODO: Change this function to take `NotNan` instead, so the caller doesn't have to 374 | /// worry about panics. 375 | pub fn real(r: f64) -> Self { 376 | let r = match ordered_float::NotNan::new(r) { 377 | Ok(r) => r, 378 | Err(_) => panic!("Number::real: got NaN"), 379 | }; 380 | Number::Real(r) 381 | } 382 | } 383 | 384 | //======================================= 385 | // Display & Debug impl/s 386 | //======================================= 387 | 388 | impl fmt::Debug for Expr { 389 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 390 | let Expr { inner } = self; 391 | write!(f, "{:?}", inner) 392 | } 393 | } 394 | 395 | /// By default, this should generate a string which can be unambiguously parsed to 396 | /// reconstruct the `Expr` being displayed. This means symbols will always include their 397 | /// contexts, special characters in String's will always be properly escaped, and numeric 398 | /// literals needing precision and accuracy marks will have them. 399 | impl fmt::Display for Expr { 400 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 401 | write!(f, "{}", self.inner) 402 | } 403 | } 404 | 405 | impl fmt::Display for ExprKind { 406 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 407 | match *self { 408 | ExprKind::Normal(ref normal) => fmt::Display::fmt(normal, f), 409 | ExprKind::Integer(ref int) => fmt::Display::fmt(int, f), 410 | ExprKind::Real(ref real) => fmt::Display::fmt(real, f), 411 | ExprKind::String(ref string) => { 412 | // Escape any '"' which appear in the string. 413 | // Using the Debug implementation will cause \n, \t, etc. to appear in 414 | // place of the literal character they are escapes for. This is necessary 415 | // when printing expressions in a way that they can be read back in as a 416 | // string, such as with ToExpression. 417 | write!(f, "{:?}", string) 418 | }, 419 | ExprKind::Symbol(ref symbol) => fmt::Display::fmt(symbol, f), 420 | } 421 | } 422 | } 423 | 424 | impl fmt::Debug for ExprKind { 425 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 426 | write!(f, "{}", self) 427 | } 428 | } 429 | 430 | impl fmt::Display for Normal { 431 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 432 | write!(f, "{}[", self.head)?; 433 | for (idx, elem) in self.contents.iter().enumerate() { 434 | write!(f, "{}", elem)?; 435 | if idx != self.contents.len() - 1 { 436 | write!(f, ", ")?; 437 | } 438 | } 439 | write!(f, "]") 440 | } 441 | } 442 | 443 | impl fmt::Display for Number { 444 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 445 | match *self { 446 | Number::Integer(ref int) => write!(f, "{}", int), 447 | Number::Real(ref real) => { 448 | // Make sure we're not printing NotNan (which surprisingly implements 449 | // Display) 450 | let real: f64 = **real; 451 | write!(f, "{:?}", real) 452 | }, 453 | } 454 | } 455 | } 456 | 457 | //====================================== 458 | // Comparision trait impls 459 | //====================================== 460 | 461 | impl PartialEq for Expr { 462 | fn eq(&self, other: &Symbol) -> bool { 463 | match self.kind() { 464 | ExprKind::Symbol(self_sym) => self_sym == other, 465 | _ => false, 466 | } 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/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 | 28 | pub(crate) mod parse; 29 | 30 | use std::{ 31 | fmt::{self, Debug, Display}, 32 | mem, 33 | sync::Arc, 34 | }; 35 | 36 | 37 | /* Notes 38 | 39 | Operations 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. 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)] 71 | pub struct Symbol(Arc); 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)] 77 | pub struct SymbolName(Arc); 78 | 79 | /// Wolfram Language context. 80 | /// 81 | /// Examples: `` System` ``, `` Global` ``, `` MyPackage`Utils` ``, etc. 82 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 83 | pub struct Context(Arc); 84 | 85 | /// Context begining with a `` ` ``. 86 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 87 | pub struct RelativeContext(Arc); 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. 92 | const _: () = assert!(mem::size_of::() == mem::size_of::()); 93 | const _: () = assert!(mem::align_of::() == mem::align_of::()); 94 | 95 | //====================================== 96 | // Borrowed Data 97 | //====================================== 98 | 99 | /// Borrowed string containing a valid symbol. 100 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 101 | pub struct SymbolRef<'s>(&'s str); 102 | 103 | /// Borrowing string containing a valid symbol name. 104 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 105 | pub struct SymbolNameRef<'s>(&'s str); 106 | 107 | /// Borrowed string containing a valid context. 108 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 109 | pub struct ContextRef<'s>(pub(super) &'s str); 110 | 111 | //========================================================== 112 | // Impls -- Owned Types 113 | //========================================================== 114 | 115 | impl From<&Symbol> for Symbol { 116 | fn from(sym: &Symbol) -> Self { 117 | sym.clone() 118 | } 119 | } 120 | 121 | impl 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 { 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 | 176 | impl 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 { 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 | 192 | impl Context { 193 | /// Attempt to parse `input` as a context. 194 | pub fn try_new(input: &str) -> Option { 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 { 268 | let Context(string) = self; 269 | 270 | let comps: Vec = 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 | 294 | impl RelativeContext { 295 | /// Attempt to parse `input` as a relative context. 296 | pub fn try_new(input: &str) -> Option { 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 { 314 | let RelativeContext(string) = self; 315 | 316 | let comps: Vec = 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 | 330 | macro_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>(input: S) -> $ty { 361 | let inner: Arc = Arc::new(input.into()); 362 | $ty(inner) 363 | } 364 | } 365 | }; 366 | } 367 | 368 | common_impls!(impl Symbol); 369 | common_impls!(impl SymbolName); 370 | common_impls!(impl Context); 371 | common_impls!(impl RelativeContext); 372 | 373 | //========================================================== 374 | // Impls -- Borrowed Types 375 | //========================================================== 376 | 377 | impl<'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 { 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 | 443 | impl<'s> SymbolNameRef<'s> { 444 | /// Attempt to parse `string` as a symbol name. 445 | pub fn try_new(string: &'s str) -> Option { 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 | 467 | impl<'s> ContextRef<'s> { 468 | /// Attempt to parse `string` as a context. 469 | pub fn try_new(string: &'s str) -> Option { 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 | 495 | impl Display for SymbolNameRef<'_> { 496 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 497 | write!(f, "{}", self.as_str()) 498 | } 499 | } 500 | --------------------------------------------------------------------------------