Skip to main content

sashite_pin/
identifier.rs

1//! The central PIN identifier type.
2
3use crate::encode::EncodedPin;
4use crate::error::ParseError;
5use crate::letter::Letter;
6use crate::side::Side;
7use crate::state::State;
8
9/// A parsed PIN token: the identity of a piece at the level of notation.
10///
11/// An `Identifier` bundles the four attributes a token encodes — a
12/// [`Letter`] abbreviation, a [`Side`], a [`State`], and a terminal flag — into
13/// a single 4-byte `Copy` value. Construction from typed components via
14/// [`Identifier::new`] is total: every combination is a valid token, so it
15/// cannot fail.
16///
17/// The derived total ordering compares attributes in the order
18/// letter → side → state → terminal.
19///
20/// # Examples
21///
22/// ```
23/// # fn main() -> Result<(), sashite_pin::ParseError> {
24/// use sashite_pin::{Identifier, Side, State};
25///
26/// let rook: Identifier = "+r".parse()?;
27/// assert_eq!(rook.letter().as_char(), 'R');
28/// assert_eq!(rook.side(), Side::Second);
29/// assert_eq!(rook.state(), State::Enhanced);
30/// assert!(!rook.is_terminal());
31///
32/// // Transformations are cheap and infallible; the value is `Copy`.
33/// assert_eq!(rook.flipped().normalized().encode().as_str(), "R");
34/// # Ok(())
35/// # }
36/// ```
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub struct Identifier {
39    letter: Letter,
40    side: Side,
41    state: State,
42    terminal: bool,
43}
44
45impl Identifier {
46    /// Builds an identifier from its four typed components.
47    ///
48    /// This is infallible: because each component type is valid by
49    /// construction, every combination denotes a valid PIN token.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use sashite_pin::{Identifier, Letter, Side, State};
55    ///
56    /// let p = Identifier::new(
57    ///     Letter::try_from_char('R').unwrap(),
58    ///     Side::Second,
59    ///     State::Enhanced,
60    ///     false,
61    /// );
62    /// assert_eq!(p.encode().as_str(), "+r");
63    /// ```
64    #[must_use]
65    pub const fn new(letter: Letter, side: Side, state: State, terminal: bool) -> Self {
66        Self {
67            letter,
68            side,
69            state,
70            terminal,
71        }
72    }
73
74    /// Parses a string slice into an identifier.
75    ///
76    /// # Errors
77    ///
78    /// Returns a [`ParseError`] if `input` is not a valid PIN token (empty,
79    /// too long, or malformed).
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// # fn main() -> Result<(), sashite_pin::ParseError> {
85    /// use sashite_pin::Identifier;
86    ///
87    /// let king = Identifier::parse("K^")?;
88    /// assert!(king.is_first());
89    /// assert!(king.is_terminal());
90    /// # Ok(())
91    /// # }
92    /// ```
93    pub const fn parse(input: &str) -> Result<Self, ParseError> {
94        crate::parse::parse(input)
95    }
96
97    /// Reports whether `input` is a valid PIN token, without allocating or
98    /// constructing an identifier on the caller's side.
99    #[must_use]
100    pub const fn is_valid(input: &str) -> bool {
101        Self::parse(input).is_ok()
102    }
103
104    /// Returns the canonical, allocation-free string encoding of this token.
105    #[must_use]
106    pub fn encode(self) -> EncodedPin {
107        EncodedPin::from_identifier(self)
108    }
109
110    // --- Accessors ---
111
112    /// Returns the piece-name abbreviation (always uppercase).
113    #[must_use]
114    pub const fn letter(self) -> Letter {
115        self.letter
116    }
117
118    /// Returns the side the piece belongs to.
119    #[must_use]
120    pub const fn side(self) -> Side {
121        self.side
122    }
123
124    /// Returns the piece state.
125    #[must_use]
126    pub const fn state(self) -> State {
127        self.state
128    }
129
130    /// Reports whether the piece is terminal (the `^` marker is present).
131    #[must_use]
132    pub const fn is_terminal(self) -> bool {
133        self.terminal
134    }
135
136    // --- State queries ---
137
138    /// Reports whether the state is [`State::Normal`].
139    #[must_use]
140    pub const fn is_normal(self) -> bool {
141        matches!(self.state, State::Normal)
142    }
143
144    /// Reports whether the state is [`State::Enhanced`].
145    #[must_use]
146    pub const fn is_enhanced(self) -> bool {
147        matches!(self.state, State::Enhanced)
148    }
149
150    /// Reports whether the state is [`State::Diminished`].
151    #[must_use]
152    pub const fn is_diminished(self) -> bool {
153        matches!(self.state, State::Diminished)
154    }
155
156    // --- Side queries ---
157
158    /// Reports whether the side is [`Side::First`].
159    #[must_use]
160    pub const fn is_first(self) -> bool {
161        matches!(self.side, Side::First)
162    }
163
164    /// Reports whether the side is [`Side::Second`].
165    #[must_use]
166    pub const fn is_second(self) -> bool {
167        matches!(self.side, Side::Second)
168    }
169
170    // --- Transformations (return a new value; the type is `Copy`) ---
171
172    /// Returns a copy with the abbreviation replaced.
173    #[must_use]
174    pub const fn with_letter(self, letter: Letter) -> Self {
175        Self::new(letter, self.side, self.state, self.terminal)
176    }
177
178    /// Returns a copy with the side replaced.
179    #[must_use]
180    pub const fn with_side(self, side: Side) -> Self {
181        Self::new(self.letter, side, self.state, self.terminal)
182    }
183
184    /// Returns a copy with the state replaced.
185    #[must_use]
186    pub const fn with_state(self, state: State) -> Self {
187        Self::new(self.letter, self.side, state, self.terminal)
188    }
189
190    /// Returns a copy with the terminal flag replaced.
191    #[must_use]
192    pub const fn with_terminal(self, terminal: bool) -> Self {
193        Self::new(self.letter, self.side, self.state, terminal)
194    }
195
196    /// Returns a copy in the [`State::Enhanced`] state.
197    #[must_use]
198    pub const fn enhanced(self) -> Self {
199        self.with_state(State::Enhanced)
200    }
201
202    /// Returns a copy in the [`State::Diminished`] state.
203    #[must_use]
204    pub const fn diminished(self) -> Self {
205        self.with_state(State::Diminished)
206    }
207
208    /// Returns a copy in the baseline [`State::Normal`] state.
209    #[must_use]
210    pub const fn normalized(self) -> Self {
211        self.with_state(State::Normal)
212    }
213
214    /// Returns a copy belonging to the opposite [`Side`].
215    #[must_use]
216    pub const fn flipped(self) -> Self {
217        self.with_side(self.side.flip())
218    }
219}
220
221impl core::fmt::Display for Identifier {
222    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
223        f.write_str(self.encode().as_str())
224    }
225}
226
227impl core::str::FromStr for Identifier {
228    type Err = ParseError;
229
230    fn from_str(s: &str) -> Result<Self, Self::Err> {
231        Self::parse(s)
232    }
233}
234
235impl TryFrom<&str> for Identifier {
236    type Error = ParseError;
237
238    fn try_from(s: &str) -> Result<Self, Self::Error> {
239        Self::parse(s)
240    }
241}
242
243impl TryFrom<&[u8]> for Identifier {
244    type Error = ParseError;
245
246    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
247        crate::parse::parse_bytes(bytes)
248    }
249}