sashite-feen 0.1.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
//! # Field Expression Encoding Notation (FEEN)
//!
//! A `no_std`, `unsafe`-free, allocation-free implementation of the
//! [FEEN v1.0.0 specification](https://sashite.dev/specs/feen/1.0.0/), built on
//! [EPIN](https://sashite.dev/specs/epin/1.0.0/) (piece tokens) and
//! [SIN](https://sashite.dev/specs/sin/1.0.0/) (style tokens).
//!
//! FEEN encodes a complete board-game **position** as three space-separated
//! fields:
//!
//! ```text
//! <piece-placement> <hands> <style-turn>
//! ```
//!
//! for example the chess starting position
//! `-rnbqk^bn-r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/-RNBQK^BN-R / C/c`.
//!
//! ## Allocation-free by design
//!
//! Unlike a fixed-size token, a position is variable-sized, so this crate is
//! built as a **borrowing, streaming validator** rather than a parser that
//! returns an owned value:
//!
//! - [`Feen::parse`] validates an input string in a single pass and returns a
//!   [`Feen`] *view* that borrows it — nothing is materialized, nothing is
//!   allocated.
//! - The view exposes the geometry ([`Feen::shape`]), the square and piece
//!   counts, the active player, and **lazy iterators** over board squares and
//!   hand items.
//! - An owned position is available behind the `alloc` feature as a
//!   [`sashite_qi::Qi`], produced by [`Feen::to_qi`]; [`encode`] turns one back
//!   into a canonical FEEN string. That conversion is the only part of the crate
//!   that allocates.
//!
//! The default build does not even link the `alloc` crate.
//!
//! ## Bounds (assumed for safety)
//!
//! Inputs are bounded before and during parsing, which keeps memory and time
//! bounded even on untrusted input:
//!
//! - at most [`MAX_STRING_LENGTH`] bytes (checked before any parsing),
//! - at most [`MAX_DIMENSIONS`] board dimensions,
//! - at most [`MAX_DIMENSION_SIZE`] cells along any dimension,
//! - **regular shapes only**: every rank within a dimension has the same length.
//!
//! The last point makes this crate intentionally *stricter* than the FEEN
//! specification, which also permits irregular boards: such inputs are rejected
//! with [`ParseError::BoardNotRegular`].
//!
//! ## Example
//!
//! ```
//! # fn main() -> Result<(), sashite_feen::ParseError> {
//! use sashite_feen::{Feen, Side};
//!
//! let feen = Feen::parse("8/8/8/8/8/8/8/8 / C/c")?;
//! assert_eq!(feen.square_count(), 64);
//! assert_eq!(feen.piece_count(), 0);
//! assert_eq!(feen.active_side(), Side::First);
//!
//! assert!(Feen::is_valid("k^+p4+PK^ / C/c")); // a 1D, 8-square board
//! # Ok(())
//! # }
//! ```
//!
//! ## Guarantees
//!
//! - **`no_std` and allocation-free core.** Validation and iteration borrow the
//!   input; nothing touches the heap unless the `alloc` feature is used.
//! - **No `unsafe`, no regex engine.** Parsing is a single left-to-right pass
//!   over raw bytes, with bounded integer arithmetic.
//! - **Built on EPIN and SIN.** Piece and style tokens are validated by those
//!   crates; FEEN adds only the field, dimensional, canonicality, and
//!   cardinality rules.

// `no_std` for every normal build; under `cargo test` the crate links `std` so
// the unit-test harness (and the in-module unit tests) can run. Consumers and
// the `no_std` CI job (which builds `--lib`, not tests) still get `no_std`.
#![cfg_attr(not(test), no_std)]

#[cfg(feature = "alloc")]
extern crate alloc;

mod encode;
mod error;
mod feen;
mod hands;
mod limits;
mod parse;
mod placement;
#[cfg(feature = "serde")]
mod serde_impl;
mod shape;
mod style_turn;
mod token;

/// Re-export of the [`sashite_epin`] crate, which provides the piece-token type
/// surfaced by this crate's board and hand iterators.
pub use sashite_epin;
/// Re-export of the [`sashite_sin`] crate, which provides the style-token type
/// surfaced by the style–turn accessors.
pub use sashite_sin;

/// Re-export of the [`sashite_qi`] crate, whose [`Qi`](sashite_qi::Qi) is the
/// owned position type produced by [`Feen::to_qi`]. Available with the `alloc`
/// feature.
#[cfg(feature = "alloc")]
pub use sashite_qi;

/// The two-side model (`First` / `second`) shared across the ecosystem,
/// re-exported from [`sashite_sin`]. Used here for the active player and the
/// hand attribution.
pub use sashite_sin::Side;

pub use crate::error::ParseError;
pub use crate::feen::{Feen, SquareIter};
pub use crate::hands::{HandItem, HandIter};
pub use crate::limits::{MAX_DIMENSIONS, MAX_DIMENSION_SIZE, MAX_STRING_LENGTH};
pub use crate::shape::Shape;

/// Canonical FEEN serialization of an owned [`Qi`](sashite_qi::Qi) position.
/// Available with the `alloc` feature.
#[cfg(feature = "alloc")]
pub use crate::encode::{encode, write_feen};

/// A `#[serde(with = "…")]` adapter that (de)serializes a
/// [`Qi`](sashite_qi::Qi) position as its canonical FEEN string. Available with
/// the `serde` feature.
#[cfg(feature = "serde")]
pub use crate::serde_impl::feen_string;