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}