use crate::AlphabetError;
use std::collections::BTreeMap;
use std::fmt::{Debug, Display, Formatter};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AlphabetId(String);
impl AlphabetId {
pub fn try_new(value: impl Into<String>) -> Result<Self, AlphabetError> {
let value = value.into();
validate_stable_id(&value).map_err(|reason| AlphabetError::InvalidId {
value: value.clone(),
reason,
})?;
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for AlphabetId {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, formatter)
}
}
pub trait SerialAlphabet: Clone + Eq + Debug {
type Symbol: Clone + Eq + Ord + Debug;
fn id(&self) -> &AlphabetId;
fn symbols(&self) -> &[Self::Symbol];
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FiniteAlphabet<S>
where
S: Clone + Eq + Ord + Debug,
{
id: AlphabetId,
symbols: Vec<S>,
}
impl<S> FiniteAlphabet<S>
where
S: Clone + Eq + Ord + Debug,
{
pub fn try_new(id: AlphabetId, symbols: Vec<S>) -> Result<Self, AlphabetError> {
validate_symbols(&id, &symbols)?;
Ok(Self { id, symbols })
}
pub fn position(&self, symbol: &S) -> Option<usize> {
self.symbols
.iter()
.position(|candidate| candidate == symbol)
}
}
impl<S> SerialAlphabet for FiniteAlphabet<S>
where
S: Clone + Eq + Ord + Debug,
{
type Symbol = S;
fn id(&self) -> &AlphabetId {
&self.id
}
fn symbols(&self) -> &[Self::Symbol] {
&self.symbols
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlphabetRegistry<A: SerialAlphabet> {
alphabets: BTreeMap<AlphabetId, A>,
}
impl<A: SerialAlphabet> Default for AlphabetRegistry<A> {
fn default() -> Self {
Self {
alphabets: BTreeMap::new(),
}
}
}
impl<A: SerialAlphabet> AlphabetRegistry<A> {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, alphabet: A) -> Result<(), AlphabetError> {
validate_alphabet(&alphabet)?;
if self.alphabets.contains_key(alphabet.id()) {
return Err(AlphabetError::DuplicateId(alphabet.id().clone()));
}
self.alphabets.insert(alphabet.id().clone(), alphabet);
Ok(())
}
pub fn get(&self, id: &AlphabetId) -> Option<&A> {
self.alphabets.get(id)
}
pub fn len(&self) -> usize {
self.alphabets.len()
}
pub fn is_empty(&self) -> bool {
self.alphabets.is_empty()
}
}
pub(crate) fn validate_alphabet<A: SerialAlphabet>(
alphabet: &A,
) -> Result<BTreeMap<A::Symbol, usize>, AlphabetError> {
validate_symbols(alphabet.id(), alphabet.symbols())
}
fn validate_symbols<S>(id: &AlphabetId, symbols: &[S]) -> Result<BTreeMap<S, usize>, AlphabetError>
where
S: Clone + Eq + Ord + Debug,
{
if symbols.is_empty() {
return Err(AlphabetError::Empty { id: id.clone() });
}
let mut positions = BTreeMap::new();
for (position, symbol) in symbols.iter().cloned().enumerate() {
if let Some(first) = positions.insert(symbol, position) {
return Err(AlphabetError::DuplicateSymbol {
id: id.clone(),
first,
duplicate: position,
});
}
}
Ok(positions)
}
pub(crate) fn validate_stable_id(value: &str) -> Result<(), &'static str> {
if value.is_empty() {
return Err("id must not be empty");
}
if value.starts_with('/') || value.ends_with('/') || value.contains("//") {
return Err("id must use non-empty path segments");
}
if !value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b'/'))
{
return Err("id contains a non-portable character");
}
Ok(())
}