sashite-sin 1.1.0

Style Identifier Notation (SIN): a compact, ASCII-only, no_std token encoding a player's side and style in abstract strategy board games.
Documentation
//! Allocation-free string encoding of a SIN token.

use crate::identifier::Identifier;

/// The canonical string form of an [`Identifier`], stored inline.
///
/// A token occupies exactly one byte, so `EncodedSin` keeps it in a fixed
/// one-byte buffer with no heap allocation. It is produced by
/// [`Identifier::encode`] and dereferences to [`str`], so it can be used
/// wherever a string slice is expected.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), sashite_sin::ParseError> {
/// use sashite_sin::Identifier;
///
/// let enc = Identifier::parse("W")?.encode();
/// assert_eq!(enc.as_str(), "W");
/// assert_eq!(&*enc, "W"); // via Deref<Target = str>
/// assert_eq!(enc.len(), 1); // str method reached through Deref
/// assert_eq!(enc, "W"); // direct comparison via PartialEq<&str>
/// assert_eq!("W", enc); // and the reverse direction
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Copy)]
pub struct EncodedSin {
    buf: [u8; 1],
}

impl EncodedSin {
    /// Encodes an identifier into its canonical token form: the single
    /// abbreviation letter, cased according to the side.
    #[must_use]
    pub(crate) const fn from_identifier(id: Identifier) -> Self {
        Self {
            buf: [id.letter().to_ascii(id.side())],
        }
    }

    /// Returns the encoded token as a string slice.
    #[must_use]
    pub fn as_str(&self) -> &str {
        debug_assert!(
            self.buf.is_ascii(),
            "EncodedSin must contain only ASCII bytes"
        );
        // ASCII is always valid UTF-8, so this conversion cannot fail; the empty
        // fallback is unreachable and exists only to avoid `unsafe`.
        core::str::from_utf8(&self.buf).unwrap_or("")
    }
}

impl core::ops::Deref for EncodedSin {
    type Target = str;

    fn deref(&self) -> &str {
        self.as_str()
    }
}

impl AsRef<str> for EncodedSin {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

impl core::fmt::Display for EncodedSin {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        // `pad`, not `write_str`: a width, fill, alignment or precision in the
        // format spec must be honoured, the way `str` and `char` honour it —
        // and the way `Identifier`'s own `Display` does, so the two agree.
        f.pad(self.as_str())
    }
}

impl core::fmt::Debug for EncodedSin {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "EncodedSin({:?})", self.as_str())
    }
}

// A token is a single byte, so the derived comparisons are the byte's. These
// are additive: before them, two `EncodedSin` values could be compared to a
// `str` but not to each other, so `assert_eq!(a.encode(), b.encode())` did not
// compile.
impl PartialEq for EncodedSin {
    fn eq(&self, other: &Self) -> bool {
        self.buf == other.buf
    }
}

impl Eq for EncodedSin {}

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

impl PartialOrd for EncodedSin {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for EncodedSin {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.buf.cmp(&other.buf)
    }
}

impl PartialEq<str> for EncodedSin {
    fn eq(&self, other: &str) -> bool {
        self.as_str() == other
    }
}

impl PartialEq<&str> for EncodedSin {
    fn eq(&self, other: &&str) -> bool {
        self.as_str() == *other
    }
}

impl PartialEq<EncodedSin> for str {
    fn eq(&self, other: &EncodedSin) -> bool {
        self == other.as_str()
    }
}

impl PartialEq<EncodedSin> for &str {
    fn eq(&self, other: &EncodedSin) -> bool {
        *self == other.as_str()
    }
}