1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! # 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.
//!
//! Going the other way is fallible: `Qi` is a more permissive model than FEEN,
//! so [`encode`] returns an [`EncodeError`] for a position FEEN cannot spell
//! rather than a string [`Feen::parse`] would reject. Whatever it returns as
//! `Ok`, this crate parses back into the position it started from.
//!
//! 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,
//! - at most [`MAX_SQUARE_COUNT`] squares in total,
//! - **regular shapes only**: every rank within a dimension has the same length.
//!
//! **All five make this crate intentionally *stricter* than the FEEN
//! specification**, which places no ceiling on dimensionality, dimension size or
//! square count, and which also permits irregular boards. The corresponding
//! rejections are [`ParseError::InputTooLong`],
//! [`ParseError::TooManyDimensions`], [`ParseError::DimensionTooLarge`],
//! [`ParseError::TooManySquares`] and [`ParseError::BoardNotRegular`]. Each
//! bound sits far beyond any real board, and none of them changes how a string
//! the crate *does* accept is interpreted.
//!
//! The byte cap and the square cap bound different things, and which one binds
//! depends on how full the board is: an empty 255 × 255 board spells out in
//! 1 019 bytes and is accepted, while a fully occupied one would need 65 279 and
//! is not. See [`MAX_STRING_LENGTH`] for the figures.
//!
//! ## 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`.
extern crate alloc;
/// 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.
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 Side;
pub use crateParseError;
pub use crate;
pub use crate;
pub use crate;
pub use crateShape;
/// Canonical FEEN serialization of an owned [`Qi`](sashite_qi::Qi) position,
/// and the reasons it can fail. Available with the `alloc` feature.
pub use crate;
pub use crateEncodeError;
/// A `#[serde(with = "…")]` adapter that (de)serializes a
/// [`Qi`](sashite_qi::Qi) position as its canonical FEEN string. Available with
/// the `serde` feature.
pub use cratefeen_string;
/// Compiles and runs the README's examples as doctests.
///
/// The README is the crate's front page on crates.io and the first code a
/// reader copies, so it is held to the same standard as the rustdoc above: if
/// an example there stops compiling, `cargo test` fails. `include_str!` also
/// means the file cannot drift out of the published tarball's reach — it is the
/// same bytes in both places.
;