sashite-epin 1.0.0

Extended Piece Identifier Notation (EPIN): a compact, ASCII-only, no_std token format that extends PIN with a native/derived style marker for abstract strategy board games.
Documentation
//! The central EPIN identifier type.

use sashite_pin::{Letter, Side, State};

use crate::encode::EncodedEpin;
use crate::error::ParseError;

/// A parsed EPIN token: a PIN identity plus a native/derived style flag.
///
/// An `Identifier` pairs a PIN [`sashite_pin::Identifier`] with a single
/// boolean: the style status is *native* when the derivation marker is absent
/// and *derived* when it is present. All four PIN attributes are reachable
/// either directly (via the delegating accessors) or through [`pin`], which
/// returns the underlying PIN identifier and thus the full PIN API.
///
/// The derived total ordering compares the PIN identifier first, then orders
/// native (`false`) before derived (`true`).
///
/// [`pin`]: Identifier::pin
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), sashite_epin::ParseError> {
/// use sashite_epin::Identifier;
///
/// let rook: Identifier = "r'".parse()?;
/// assert_eq!(rook.letter().as_char(), 'R');
/// assert!(rook.is_second());
/// assert!(rook.is_derived());
///
/// // `native`/`derive` only flip the style flag and are idempotent.
/// assert_eq!(rook.native().encode().as_str(), "r");
/// assert_eq!(rook.native().native().encode().as_str(), "r");
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Identifier {
    pin: sashite_pin::Identifier,
    derived: bool,
}

impl Identifier {
    /// Builds an EPIN identifier from a PIN identifier and a derivation flag.
    ///
    /// `derived = false` denotes native style status, `true` denotes derived.
    /// This is infallible: every PIN identifier is a valid EPIN core.
    ///
    /// # Examples
    ///
    /// ```
    /// use sashite_epin::Identifier;
    /// use sashite_pin::Identifier as Pin;
    ///
    /// let pin = Pin::parse("+r").unwrap();
    /// let derived = Identifier::new(pin, true);
    /// assert_eq!(derived.encode().as_str(), "+r'");
    /// ```
    #[must_use]
    pub const fn new(pin: sashite_pin::Identifier, derived: bool) -> Self {
        Self { pin, derived }
    }

    /// Parses a string slice into an identifier.
    ///
    /// # Errors
    ///
    /// Returns [`ParseError::DerivationMarker`] if a `'` appears anywhere but as
    /// the final character, or [`ParseError::Pin`] if the PIN core is otherwise
    /// invalid (empty, too long, or malformed).
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> Result<(), sashite_epin::ParseError> {
    /// use sashite_epin::Identifier;
    ///
    /// let king = Identifier::parse("K^'")?;
    /// assert!(king.is_terminal());
    /// assert!(king.is_derived());
    /// # Ok(())
    /// # }
    /// ```
    pub fn parse(input: &str) -> Result<Self, ParseError> {
        crate::parse::parse(input)
    }

    /// Reports whether `input` is a valid EPIN token, without constructing an
    /// identifier on the caller's side.
    #[must_use]
    pub fn is_valid(input: &str) -> bool {
        Self::parse(input).is_ok()
    }

    /// Returns the canonical, allocation-free string encoding of this token.
    #[must_use]
    pub fn encode(self) -> EncodedEpin {
        EncodedEpin::from_identifier(self)
    }

    // --- Accessors ---

    /// Returns the underlying PIN identifier.
    ///
    /// This is the escape hatch to the full PIN API (queries and
    /// transformations not re-exposed directly on the EPIN type).
    #[must_use]
    pub const fn pin(self) -> sashite_pin::Identifier {
        self.pin
    }

    /// Returns the piece-name abbreviation (always uppercase).
    #[must_use]
    pub const fn letter(self) -> Letter {
        self.pin.letter()
    }

    /// Returns the side the piece belongs to.
    #[must_use]
    pub const fn side(self) -> Side {
        self.pin.side()
    }

    /// Returns the piece state.
    #[must_use]
    pub const fn state(self) -> State {
        self.pin.state()
    }

    /// Reports whether the piece is terminal (the `^` marker is present).
    #[must_use]
    pub const fn is_terminal(self) -> bool {
        self.pin.is_terminal()
    }

    /// Reports whether the side is [`Side::First`].
    #[must_use]
    pub const fn is_first(self) -> bool {
        self.pin.is_first()
    }

    /// Reports whether the side is [`Side::Second`].
    #[must_use]
    pub const fn is_second(self) -> bool {
        self.pin.is_second()
    }

    /// Reports whether the style status is native (no derivation marker).
    #[must_use]
    pub const fn is_native(self) -> bool {
        !self.derived
    }

    /// Reports whether the style status is derived (the derivation marker is
    /// present).
    #[must_use]
    pub const fn is_derived(self) -> bool {
        self.derived
    }

    // --- Transformations (return a new value; the type is `Copy`) ---

    /// Returns a copy with native style status (derivation marker removed).
    ///
    /// Idempotent: applying it to an already-native identifier is a no-op.
    #[must_use]
    pub const fn native(self) -> Self {
        Self::new(self.pin, false)
    }

    /// Returns a copy with derived style status (derivation marker added).
    ///
    /// Idempotent: applying it to an already-derived identifier is a no-op.
    #[must_use]
    pub const fn derive(self) -> Self {
        Self::new(self.pin, true)
    }

    /// Returns a copy with the underlying PIN identifier replaced, preserving
    /// the style flag.
    ///
    /// This composes with PIN's own transformations, e.g.
    /// `e.with_pin(e.pin().flipped())` flips the side while keeping the
    /// native/derived status unchanged.
    #[must_use]
    pub const fn with_pin(self, pin: sashite_pin::Identifier) -> Self {
        Self::new(pin, self.derived)
    }
}

impl core::fmt::Display for Identifier {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(self.encode().as_str())
    }
}

impl core::str::FromStr for Identifier {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::parse(s)
    }
}

impl TryFrom<&str> for Identifier {
    type Error = ParseError;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        Self::parse(s)
    }
}

impl TryFrom<&[u8]> for Identifier {
    type Error = ParseError;

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
        crate::parse::parse_bytes(bytes)
    }
}