rusty-systems 5.0.0

A library for procedurally generating content using L-Systems
Documentation
//! Tools for handling the symbols that make up strings in the L-system ([`crate::strings::ProductionString`]).
//!
//! Symbols can be of various kinds:
//!
//! * Terminals / Constants, which are the strict endpoints of the L-System. They are not rewritten by
//!   production rules.
//! * Variable / Production symbols, are those that can be rewritten by production rules.
//!
//! Rusty-Systems does not keep track of, or enforce, these kinds.
//!
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::{OnceLock, RwLock};
use std::sync::atomic::{AtomicU32, Ordering};
use crate::error::Error;
use crate::symbols;

pub mod iterator;

type CodeStoreType = RwLock<HashMap<String, u32>>;
type NameStoreType = RwLock<HashMap<u32, String>>;

static CODE_REGISTER: OnceLock<CodeStoreType> = OnceLock::new();
static NAME_REGISTER: OnceLock<NameStoreType> = OnceLock::new();
static SYMBOL_ID: AtomicU32 = AtomicU32::new(100);

/// Attempts to return a symbol code for a string.
/// 
/// While symbols are described using names when writing 
/// our L-System strings and productions, they are represented
/// internally by 32 bit unsigned ints. Given a `name` of a symbol,
/// this function returns an unsigned int associated with that `name`.   
///
/// If the `name` was previously seen, it will return the same code.
///
/// Errors are return in the following cases:
/// * The `name` is empty, or only white space.
/// * The locks this function uses are poisoned.
///
/// This is a thread safe call.
/// 
/// <div class="warning">
/// 
/// Returned code values may not be the same between different runs of
/// anything relying on this library. 
/// 
/// </div>
pub fn get_code(name: &str) -> Result<u32, Error> {
    let mut register = get_code_register().write()?;
    let name = name.trim().to_string();

    if name.is_empty() {
        return Err(Error::general("name should not be an empty string"))
    }

    if let Some(code) = register.get(&name) {
        return Ok(*code);
    }

    let code = SYMBOL_ID.fetch_add(1, Ordering::SeqCst);
    register.insert(name.clone(), code);
    drop(register);

    let mut register = get_name_register().write()?;
    register.insert(code, name);
    drop(register);

    Ok(code)
}

/// If a code was previously returned for a symbol name, this returns
/// that name.
pub fn get_name(code: u32) -> Option<String> {
    let register = get_name_register().read().ok()?;
    register.get(&code).cloned()
}

fn get_code_register() -> &'static CodeStoreType {
    CODE_REGISTER.get_or_init(|| {
        RwLock::new(HashMap::new())
    })
}

fn get_name_register() -> &'static NameStoreType {
    NAME_REGISTER.get_or_init(|| {
        RwLock::new(HashMap::new())
    })
}


#[derive(Debug, Clone, Copy)]
pub struct Symbol {
    code: u32
}

impl PartialEq for Symbol {
    fn eq(&self, other: &Self) -> bool {
        self.code == other.code
    }
}

impl Eq for Symbol {}

impl Hash for Symbol {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.code.hash(state);
    }
}

impl Symbol {
    /// Creates a new symbol from the given code.
    #[inline]
    pub fn from_code(code: u32) -> Self {
        Symbol {
            code
        }
    }

    /// Attempts to create a Symbol from the given string.
    ///
    /// This uses [`get_code`] to determine the Symbol's code. This
    /// function returns the same errors.
    pub fn build<S: AsRef<str>>(name: S) -> Result<Symbol, Error>{
        let name = name.as_ref();
        Ok(Self::from_code(get_code(name)?))
    }

    /// A unique identifier for the symbols.
    /// 
    /// This identifier is set when the symbol is created (see [`Symbol::from_code`]).
    /// The value may not be the same when generated by different instances of [`crate::prelude::System`],
    /// and the value here should not be relied on.
    pub fn code(&self) -> u32 {
        self.code
    }
    
    /// Returns the name associated with the symbol. 
    pub fn name(&self) -> Option<String> {
        get_name(self.code)
    }
}

impl Display for Symbol {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if let Some(name) = get_name(self.code) {
            return f.write_str(name.as_str());
        }

        write!(f, "code:{}", self.code)
    }
}

// todo document symbol store.
pub trait SymbolStore {
    fn add_symbol(&self, name: &str) -> crate::Result<Symbol>;
    fn get_symbol(&self, name: &str) -> Option<Symbol>;
}

impl SymbolStore for RefCell<HashSet<u32>> {
    fn add_symbol(&self, name: &str) -> crate::Result<Symbol> {
        let code = symbols::get_code(name)?;

        let mut map = self.borrow_mut();
        map.insert(code);

        Ok(Symbol::from_code(code))
    }

    fn get_symbol(&self, name: &str) -> Option<Symbol> {
        let code = get_code(name).ok()?;
        let symbols = self.borrow();

        symbols.get(&code)
            .cloned()
            .map(Symbol::from_code)
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn register_is_idempotent() {
        let hello = get_code("Hello").unwrap();
        let bye = get_code("bye").unwrap();

        assert_eq!(get_code("Hello").unwrap(), hello);
        assert_ne!(get_code("Hello").unwrap(), bye);
    }

    #[test]
    fn register_records_names() {
        let hello = "hello";
        let hcode = get_code(hello).unwrap();

        assert_eq!(hello, get_name(hcode).unwrap());
    }

    #[test]
    fn no_name() {
        assert!(get_name(43_432_444).is_none());
    }

    #[test]
    fn empty_string_is_error() {
        assert!(get_code("").is_err());
        assert!(get_code("  ").is_err());

        assert!(get_code("  d").is_ok());
    }

    #[test]
    fn names_are_trimmed() {
        let code = get_code("  d  ").unwrap();
        let code2 = get_code("d").unwrap();
        assert_eq!(code, code2);

        assert_eq!(get_name(code).unwrap(), "d");
        assert_eq!(get_name(code2).unwrap(), "d");
    }
}