sashite-feen 0.2.0

Field Expression Encoding Notation (FEEN): a compact, ASCII-only, no_std, zero-allocation validator and encoder for board-game positions in abstract strategy games, built on EPIN and SIN.
Documentation
//! Resource bounds enforced on every input.
//!
//! These caps keep memory and time bounded even on untrusted input, which is
//! what lets the parser be a single-pass, allocation-free, panic-free validator:
//! the length check runs before any parsing, the dimension count bounds a
//! fixed-size coherence stack, and the per-dimension cap keeps every dimension
//! size in one byte.

/// Maximum length, in bytes, of a FEEN string.
///
/// Any longer input is rejected with [`crate::ParseError::InputTooLong`] before
/// a single byte is parsed.
///
/// # This is the binding constraint on *occupied* boards
///
/// The byte cap and [`MAX_SQUARE_COUNT`] bound different things, and for a board
/// with pieces on it the byte cap is the one that bites first. A piece token
/// costs 1 to 4 bytes, an empty run costs 1 to 3 bytes however long it is, so:
///
/// | board | placement field |
/// |---|---|
/// | empty 255×255 (`255/255/…`) | 1 019 bytes — accepted |
/// | fully occupied 255×255, one-byte tokens | **65 279 bytes** — rejected |
/// | fully occupied, largest square that still fits (e.g. 45×92) | 4 096 bytes — accepted |
///
/// So a position may hold up to [`MAX_SQUARE_COUNT`] squares as long as most of
/// them are empty, and roughly 4 090 squares when every one is occupied. Both
/// figures are far beyond any real board (chess 64, shōgi 81, xiangqi 90,
/// 3-D Raumschach 125), which is what 4 KiB is chosen against; it is not chosen
/// to cover a full board at the square cap.
///
/// The encoder honours the same cap: [`encode`](crate::encode) refuses a
/// position whose canonical form would exceed it rather than return a string
/// [`Feen::parse`](crate::Feen::parse) would reject.
pub const MAX_STRING_LENGTH: usize = 4096;

/// Maximum number of board dimensions (1D, 2D, or 3D).
///
/// This is a `usize` so it can size the fixed-length arrays that hold a board's
/// shape ([`crate::Shape`]) and drive the dimensional-coherence check without
/// any heap allocation. A deeper separator nesting is rejected with
/// [`crate::ParseError::TooManyDimensions`].
pub const MAX_DIMENSIONS: usize = 3;

/// Maximum number of cells along any single dimension.
///
/// Set to [`u8::MAX`] so that every dimension size fits in one byte: this is the
/// largest cap that keeps a [`crate::Shape`] dimension a `u8` (256 would force a
/// `u16` to gain a single, practically useless value). A larger dimension is
/// rejected with [`crate::ParseError::DimensionTooLarge`].
pub const MAX_DIMENSION_SIZE: u8 = u8::MAX;

/// Maximum number of squares on a board (255 × 255).
///
/// A board with more squares is rejected with
/// [`crate::ParseError::TooManySquares`]. This matches the bound used by
/// [`sashite-qi`](https://docs.rs/sashite-qi), the position model FEEN builds
/// on: 65 025 = 255² keeps a flat square index within a `u16`, bounds the memory
/// a decoded position can occupy, and stays far above any real board (chess 64,
/// shōgi 81, xiangqi 90, 3-D Raumschach 125). The per-dimension cap still allows
/// each axis up to 255; this bound caps their product.
///
/// Reaching this many squares requires most of them to be empty — see
/// [`MAX_STRING_LENGTH`], which is what limits an occupied board.
pub const MAX_SQUARE_COUNT: u32 = 65_025;