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
//! Parsing of EPIN tokens.
//!
//! An EPIN token is a PIN token with an optional trailing derivation marker
//! (`'`). Parsing splits off that marker, rejects a marker found anywhere else,
//! and delegates the remaining core to [`sashite_pin`].

use crate::error::ParseError;
use crate::identifier::Identifier;

/// The derivation marker: it flags derived style status and is always last.
const DERIVATION_MARKER: u8 = b'\'';

/// Parses a string slice into an [`Identifier`].
pub(crate) fn parse(input: &str) -> Result<Identifier, ParseError> {
    parse_bytes(input.as_bytes())
}

/// Parses a raw byte slice into an [`Identifier`].
///
/// No UTF-8 validation is performed here: the PIN core is handed to
/// `sashite-pin`, which rejects any non-ASCII byte, and the derivation marker
/// is plain ASCII.
pub(crate) fn parse_bytes(bytes: &[u8]) -> Result<Identifier, ParseError> {
    // Split off a single trailing derivation marker, if present.
    let (core, derived) = match bytes.split_last() {
        Some((&DERIVATION_MARKER, rest)) => (rest, true),
        _ => (bytes, false),
    };

    // The marker is only ever valid as the final character; one anywhere in the
    // core means it was misplaced (e.g. `K'^`) or duplicated (e.g. `K''`).
    if core.contains(&DERIVATION_MARKER) {
        return Err(ParseError::DerivationMarker);
    }

    // Delegate the core to PIN; its error converts via `From` (see error.rs).
    let pin = sashite_pin::Identifier::try_from(core)?;
    Ok(Identifier::new(pin, derived))
}