Skip to main content

sashite_feen/
lib.rs

1//! # Field Expression Encoding Notation (FEEN)
2//!
3//! A `no_std`, `unsafe`-free, allocation-free implementation of the
4//! [FEEN v1.0.0 specification](https://sashite.dev/specs/feen/1.0.0/), built on
5//! [EPIN](https://sashite.dev/specs/epin/1.0.0/) (piece tokens) and
6//! [SIN](https://sashite.dev/specs/sin/1.0.0/) (style tokens).
7//!
8//! FEEN encodes a complete board-game **position** as three space-separated
9//! fields:
10//!
11//! ```text
12//! <piece-placement> <hands> <style-turn>
13//! ```
14//!
15//! for example the chess starting position
16//! `-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`.
17//!
18//! ## Allocation-free by design
19//!
20//! Unlike a fixed-size token, a position is variable-sized, so this crate is
21//! built as a **borrowing, streaming validator** rather than a parser that
22//! returns an owned value:
23//!
24//! - [`Feen::parse`] validates an input string in a single pass and returns a
25//!   [`Feen`] *view* that borrows it — nothing is materialized, nothing is
26//!   allocated.
27//! - The view exposes the geometry ([`Feen::shape`]), the square and piece
28//!   counts, the active player, and **lazy iterators** over board squares and
29//!   hand items.
30//! - An owned position is available behind the `alloc` feature as a
31//!   [`sashite_qi::Qi`], produced by [`Feen::to_qi`]; [`encode`] turns one back
32//!   into a canonical FEEN string. That conversion is the only part of the crate
33//!   that allocates.
34//!
35//! The default build does not even link the `alloc` crate.
36//!
37//! ## Bounds (assumed for safety)
38//!
39//! Inputs are bounded before and during parsing, which keeps memory and time
40//! bounded even on untrusted input:
41//!
42//! - at most [`MAX_STRING_LENGTH`] bytes (checked before any parsing),
43//! - at most [`MAX_DIMENSIONS`] board dimensions,
44//! - at most [`MAX_DIMENSION_SIZE`] cells along any dimension,
45//! - **regular shapes only**: every rank within a dimension has the same length.
46//!
47//! The last point makes this crate intentionally *stricter* than the FEEN
48//! specification, which also permits irregular boards: such inputs are rejected
49//! with [`ParseError::BoardNotRegular`].
50//!
51//! ## Example
52//!
53//! ```
54//! # fn main() -> Result<(), sashite_feen::ParseError> {
55//! use sashite_feen::{Feen, Side};
56//!
57//! let feen = Feen::parse("8/8/8/8/8/8/8/8 / C/c")?;
58//! assert_eq!(feen.square_count(), 64);
59//! assert_eq!(feen.piece_count(), 0);
60//! assert_eq!(feen.active_side(), Side::First);
61//!
62//! assert!(Feen::is_valid("k^+p4+PK^ / C/c")); // a 1D, 8-square board
63//! # Ok(())
64//! # }
65//! ```
66//!
67//! ## Guarantees
68//!
69//! - **`no_std` and allocation-free core.** Validation and iteration borrow the
70//!   input; nothing touches the heap unless the `alloc` feature is used.
71//! - **No `unsafe`, no regex engine.** Parsing is a single left-to-right pass
72//!   over raw bytes, with bounded integer arithmetic.
73//! - **Built on EPIN and SIN.** Piece and style tokens are validated by those
74//!   crates; FEEN adds only the field, dimensional, canonicality, and
75//!   cardinality rules.
76
77// `no_std` for every normal build; under `cargo test` the crate links `std` so
78// the unit-test harness (and the in-module unit tests) can run. Consumers and
79// the `no_std` CI job (which builds `--lib`, not tests) still get `no_std`.
80#![cfg_attr(not(test), no_std)]
81
82#[cfg(feature = "alloc")]
83extern crate alloc;
84
85mod encode;
86mod error;
87mod feen;
88mod hands;
89mod limits;
90mod parse;
91mod placement;
92#[cfg(feature = "serde")]
93mod serde_impl;
94mod shape;
95mod style_turn;
96mod token;
97
98/// Re-export of the [`sashite_epin`] crate, which provides the piece-token type
99/// surfaced by this crate's board and hand iterators.
100pub use sashite_epin;
101/// Re-export of the [`sashite_sin`] crate, which provides the style-token type
102/// surfaced by the style–turn accessors.
103pub use sashite_sin;
104
105/// Re-export of the [`sashite_qi`] crate, whose [`Qi`](sashite_qi::Qi) is the
106/// owned position type produced by [`Feen::to_qi`]. Available with the `alloc`
107/// feature.
108#[cfg(feature = "alloc")]
109pub use sashite_qi;
110
111/// The two-side model (`First` / `second`) shared across the ecosystem,
112/// re-exported from [`sashite_sin`]. Used here for the active player and the
113/// hand attribution.
114pub use sashite_sin::Side;
115
116pub use crate::error::ParseError;
117pub use crate::feen::{Feen, SquareIter};
118pub use crate::hands::{HandItem, HandIter};
119pub use crate::limits::{MAX_DIMENSIONS, MAX_DIMENSION_SIZE, MAX_STRING_LENGTH};
120pub use crate::shape::Shape;
121
122/// Canonical FEEN serialization of an owned [`Qi`](sashite_qi::Qi) position.
123/// Available with the `alloc` feature.
124#[cfg(feature = "alloc")]
125pub use crate::encode::{encode, write_feen};
126
127/// A `#[serde(with = "…")]` adapter that (de)serializes a
128/// [`Qi`](sashite_qi::Qi) position as its canonical FEEN string. Available with
129/// the `serde` feature.
130#[cfg(feature = "serde")]
131pub use crate::serde_impl::feen_string;