Skip to main content

sim_lib_serial_core/
alphabet.rs

1//! Stable alphabet identity and finite symbol collections.
2
3use crate::AlphabetError;
4use std::collections::BTreeMap;
5use std::fmt::{Debug, Display, Formatter};
6
7/// Stable, portable identity of a finite serial alphabet.
8///
9/// Valid ids contain ASCII letters, digits, `.`, `_`, `-`, or `/`, do not
10/// begin or end with `/`, and do not contain empty path segments.
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct AlphabetId(String);
13
14impl AlphabetId {
15    /// Validates and constructs an alphabet id.
16    pub fn try_new(value: impl Into<String>) -> Result<Self, AlphabetError> {
17        let value = value.into();
18        validate_stable_id(&value).map_err(|reason| AlphabetError::InvalidId {
19            value: value.clone(),
20            reason,
21        })?;
22        Ok(Self(value))
23    }
24
25    /// Returns the stable text identity.
26    pub fn as_str(&self) -> &str {
27        &self.0
28    }
29}
30
31impl Display for AlphabetId {
32    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
33        Display::fmt(&self.0, formatter)
34    }
35}
36
37/// A finite, ordered vocabulary used by a [`crate::Series`].
38pub trait SerialAlphabet: Clone + Eq + Debug {
39    /// Symbol value retained in a series order.
40    type Symbol: Clone + Eq + Ord + Debug;
41
42    /// Stable alphabet identity.
43    fn id(&self) -> &AlphabetId;
44
45    /// Symbols in canonical alphabet order.
46    fn symbols(&self) -> &[Self::Symbol];
47}
48
49/// Reusable owned implementation of [`SerialAlphabet`].
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct FiniteAlphabet<S>
52where
53    S: Clone + Eq + Ord + Debug,
54{
55    id: AlphabetId,
56    symbols: Vec<S>,
57}
58
59impl<S> FiniteAlphabet<S>
60where
61    S: Clone + Eq + Ord + Debug,
62{
63    /// Constructs a non-empty alphabet with unique canonical symbols.
64    pub fn try_new(id: AlphabetId, symbols: Vec<S>) -> Result<Self, AlphabetError> {
65        validate_symbols(&id, &symbols)?;
66        Ok(Self { id, symbols })
67    }
68
69    /// Returns the canonical position of `symbol`, when it belongs to this alphabet.
70    pub fn position(&self, symbol: &S) -> Option<usize> {
71        self.symbols
72            .iter()
73            .position(|candidate| candidate == symbol)
74    }
75}
76
77impl<S> SerialAlphabet for FiniteAlphabet<S>
78where
79    S: Clone + Eq + Ord + Debug,
80{
81    type Symbol = S;
82
83    fn id(&self) -> &AlphabetId {
84        &self.id
85    }
86
87    fn symbols(&self) -> &[Self::Symbol] {
88        &self.symbols
89    }
90}
91
92/// A same-type alphabet registry that rejects duplicate stable identities.
93///
94/// The registry is deliberately a value-layer helper, not a global singleton.
95#[derive(Clone, Debug, PartialEq, Eq)]
96pub struct AlphabetRegistry<A: SerialAlphabet> {
97    alphabets: BTreeMap<AlphabetId, A>,
98}
99
100impl<A: SerialAlphabet> Default for AlphabetRegistry<A> {
101    fn default() -> Self {
102        Self {
103            alphabets: BTreeMap::new(),
104        }
105    }
106}
107
108impl<A: SerialAlphabet> AlphabetRegistry<A> {
109    /// Constructs an empty registry.
110    pub fn new() -> Self {
111        Self::default()
112    }
113
114    /// Validates and inserts an alphabet, rejecting an id already in the registry.
115    pub fn insert(&mut self, alphabet: A) -> Result<(), AlphabetError> {
116        validate_alphabet(&alphabet)?;
117        if self.alphabets.contains_key(alphabet.id()) {
118            return Err(AlphabetError::DuplicateId(alphabet.id().clone()));
119        }
120        self.alphabets.insert(alphabet.id().clone(), alphabet);
121        Ok(())
122    }
123
124    /// Looks up an alphabet by stable identity.
125    pub fn get(&self, id: &AlphabetId) -> Option<&A> {
126        self.alphabets.get(id)
127    }
128
129    /// Returns the number of registered alphabets.
130    pub fn len(&self) -> usize {
131        self.alphabets.len()
132    }
133
134    /// Returns whether no alphabets are registered.
135    pub fn is_empty(&self) -> bool {
136        self.alphabets.is_empty()
137    }
138}
139
140pub(crate) fn validate_alphabet<A: SerialAlphabet>(
141    alphabet: &A,
142) -> Result<BTreeMap<A::Symbol, usize>, AlphabetError> {
143    validate_symbols(alphabet.id(), alphabet.symbols())
144}
145
146fn validate_symbols<S>(id: &AlphabetId, symbols: &[S]) -> Result<BTreeMap<S, usize>, AlphabetError>
147where
148    S: Clone + Eq + Ord + Debug,
149{
150    if symbols.is_empty() {
151        return Err(AlphabetError::Empty { id: id.clone() });
152    }
153    let mut positions = BTreeMap::new();
154    for (position, symbol) in symbols.iter().cloned().enumerate() {
155        if let Some(first) = positions.insert(symbol, position) {
156            return Err(AlphabetError::DuplicateSymbol {
157                id: id.clone(),
158                first,
159                duplicate: position,
160            });
161        }
162    }
163    Ok(positions)
164}
165
166pub(crate) fn validate_stable_id(value: &str) -> Result<(), &'static str> {
167    if value.is_empty() {
168        return Err("id must not be empty");
169    }
170    if value.starts_with('/') || value.ends_with('/') || value.contains("//") {
171        return Err("id must use non-empty path segments");
172    }
173    if !value
174        .bytes()
175        .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b'/'))
176    {
177        return Err("id contains a non-portable character");
178    }
179    Ok(())
180}