sashite-sin 1.0.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
//! Internal byte-level parser for SIN tokens.
//!
//! This is the only place untrusted input is inspected. The parser is
//! allocation-free, uses no regex engine, and rejects over-long input on a
//! structural length check before any byte is examined. It is reached from
//! outside the crate only through [`crate::Identifier`] (its `parse`,
//! [`core::str::FromStr`], and [`TryFrom`] entry points).

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

/// Parses a string slice into an [`Identifier`].
pub(crate) const 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 needed: only a single ASCII byte can form a valid
/// token, and any other byte falls through to an error arm. Dispatches on the
/// byte length first; input longer than one byte is rejected as
/// [`ParseError::TooLong`] without inspecting its contents.
///
/// Length is measured in *bytes*: a multi-byte non-ASCII character (e.g. `'é'`,
/// two bytes) therefore yields [`ParseError::TooLong`], whereas a single
/// non-letter byte yields [`ParseError::InvalidLetter`]. Both are correct
/// rejections; only the reported variant differs.
pub(crate) const fn parse_bytes(bytes: &[u8]) -> Result<Identifier, ParseError> {
    match bytes {
        [] => Err(ParseError::Empty),
        [b0] => parse_bare(*b0),
        _ => Err(ParseError::TooLong),
    }
}

/// `<abbr>`: a lone abbreviation letter, whose case carries the side.
const fn parse_bare(byte: u8) -> Result<Identifier, ParseError> {
    match Letter::from_ascii(byte) {
        Some((letter, side)) => Ok(Identifier::new(letter, side)),
        None => Err(ParseError::InvalidLetter),
    }
}