Skip to main content

rusty_systems/
symbols.rs

1//! Tools for handling the symbols that make up strings in the L-system ([`crate::strings::ProductionString`]).
2//!
3//! Symbols can be of various kinds:
4//!
5//! * Terminals / Constants, which are the strict endpoints of the L-System. They are not rewritten by
6//!   production rules.
7//! * Variable / Production symbols, are those that can be rewritten by production rules.
8//!
9//! Rusty-Systems does not keep track of, or enforce, these kinds.
10//!
11use std::cell::RefCell;
12use std::collections::{HashMap, HashSet};
13use std::fmt::{Display, Formatter};
14use std::hash::{Hash, Hasher};
15use std::sync::{OnceLock, RwLock};
16use std::sync::atomic::{AtomicU32, Ordering};
17use crate::error::Error;
18use crate::symbols;
19
20pub mod iterator;
21
22type CodeStoreType = RwLock<HashMap<String, u32>>;
23type NameStoreType = RwLock<HashMap<u32, String>>;
24
25static CODE_REGISTER: OnceLock<CodeStoreType> = OnceLock::new();
26static NAME_REGISTER: OnceLock<NameStoreType> = OnceLock::new();
27static SYMBOL_ID: AtomicU32 = AtomicU32::new(100);
28
29/// Attempts to return a symbol code for a string.
30/// 
31/// While symbols are described using names when writing 
32/// our L-System strings and productions, they are represented
33/// internally by 32 bit unsigned ints. Given a `name` of a symbol,
34/// this function returns an unsigned int associated with that `name`.   
35///
36/// If the `name` was previously seen, it will return the same code.
37///
38/// Errors are return in the following cases:
39/// * The `name` is empty, or only white space.
40/// * The locks this function uses are poisoned.
41///
42/// This is a thread safe call.
43/// 
44/// <div class="warning">
45/// 
46/// Returned code values may not be the same between different runs of
47/// anything relying on this library. 
48/// 
49/// </div>
50pub fn get_code(name: &str) -> Result<u32, Error> {
51    let mut register = get_code_register().write()?;
52    let name = name.trim().to_string();
53
54    if name.is_empty() {
55        return Err(Error::general("name should not be an empty string"))
56    }
57
58    if let Some(code) = register.get(&name) {
59        return Ok(*code);
60    }
61
62    let code = SYMBOL_ID.fetch_add(1, Ordering::SeqCst);
63    register.insert(name.clone(), code);
64    drop(register);
65
66    let mut register = get_name_register().write()?;
67    register.insert(code, name);
68    drop(register);
69
70    Ok(code)
71}
72
73/// If a code was previously returned for a symbol name, this returns
74/// that name.
75pub fn get_name(code: u32) -> Option<String> {
76    let register = get_name_register().read().ok()?;
77    register.get(&code).cloned()
78}
79
80fn get_code_register() -> &'static CodeStoreType {
81    CODE_REGISTER.get_or_init(|| {
82        RwLock::new(HashMap::new())
83    })
84}
85
86fn get_name_register() -> &'static NameStoreType {
87    NAME_REGISTER.get_or_init(|| {
88        RwLock::new(HashMap::new())
89    })
90}
91
92
93#[derive(Debug, Clone, Copy)]
94pub struct Symbol {
95    code: u32
96}
97
98impl PartialEq for Symbol {
99    fn eq(&self, other: &Self) -> bool {
100        self.code == other.code
101    }
102}
103
104impl Eq for Symbol {}
105
106impl Hash for Symbol {
107    fn hash<H: Hasher>(&self, state: &mut H) {
108        self.code.hash(state);
109    }
110}
111
112impl Symbol {
113    /// Creates a new symbol from the given code.
114    #[inline]
115    pub fn from_code(code: u32) -> Self {
116        Symbol {
117            code
118        }
119    }
120
121    /// Attempts to create a Symbol from the given string.
122    ///
123    /// This uses [`get_code`] to determine the Symbol's code. This
124    /// function returns the same errors.
125    pub fn build<S: AsRef<str>>(name: S) -> Result<Symbol, Error>{
126        let name = name.as_ref();
127        Ok(Self::from_code(get_code(name)?))
128    }
129
130    /// A unique identifier for the symbols.
131    /// 
132    /// This identifier is set when the symbol is created (see [`Symbol::from_code`]).
133    /// The value may not be the same when generated by different instances of [`crate::prelude::System`],
134    /// and the value here should not be relied on.
135    pub fn code(&self) -> u32 {
136        self.code
137    }
138    
139    /// Returns the name associated with the symbol. 
140    pub fn name(&self) -> Option<String> {
141        get_name(self.code)
142    }
143}
144
145impl Display for Symbol {
146    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
147        if let Some(name) = get_name(self.code) {
148            return f.write_str(name.as_str());
149        }
150
151        write!(f, "code:{}", self.code)
152    }
153}
154
155// todo document symbol store.
156pub trait SymbolStore {
157    fn add_symbol(&self, name: &str) -> crate::Result<Symbol>;
158    fn get_symbol(&self, name: &str) -> Option<Symbol>;
159}
160
161impl SymbolStore for RefCell<HashSet<u32>> {
162    fn add_symbol(&self, name: &str) -> crate::Result<Symbol> {
163        let code = symbols::get_code(name)?;
164
165        let mut map = self.borrow_mut();
166        map.insert(code);
167
168        Ok(Symbol::from_code(code))
169    }
170
171    fn get_symbol(&self, name: &str) -> Option<Symbol> {
172        let code = get_code(name).ok()?;
173        let symbols = self.borrow();
174
175        symbols.get(&code)
176            .cloned()
177            .map(Symbol::from_code)
178    }
179}
180
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn register_is_idempotent() {
188        let hello = get_code("Hello").unwrap();
189        let bye = get_code("bye").unwrap();
190
191        assert_eq!(get_code("Hello").unwrap(), hello);
192        assert_ne!(get_code("Hello").unwrap(), bye);
193    }
194
195    #[test]
196    fn register_records_names() {
197        let hello = "hello";
198        let hcode = get_code(hello).unwrap();
199
200        assert_eq!(hello, get_name(hcode).unwrap());
201    }
202
203    #[test]
204    fn no_name() {
205        assert!(get_name(43_432_444).is_none());
206    }
207
208    #[test]
209    fn empty_string_is_error() {
210        assert!(get_code("").is_err());
211        assert!(get_code("  ").is_err());
212
213        assert!(get_code("  d").is_ok());
214    }
215
216    #[test]
217    fn names_are_trimmed() {
218        let code = get_code("  d  ").unwrap();
219        let code2 = get_code("d").unwrap();
220        assert_eq!(code, code2);
221
222        assert_eq!(get_name(code).unwrap(), "d");
223        assert_eq!(get_name(code2).unwrap(), "d");
224    }
225}