1use fen4::{Position, PositionParseError};
2use std::str::FromStr;
3
4use crate::types::*;
5
6use thiserror::Error;
7
8#[derive(Error, PartialEq, Clone, Debug)]
9pub enum MoveError {
10 #[error("Basic move is malformed.")]
11 Other,
12 #[error("A move starts with O-O, but is not a correct type of move.")]
13 Castle,
14 #[error("Unable to parse basic move because {0}")]
15 PositionInvalid(#[from] PositionParseError),
16}
17impl FromStr for BasicMove {
18 type Err = MoveError;
19 fn from_str(string: &str) -> Result<Self, Self::Err> {
20 let mut iter = string.chars();
21 let start = iter.next().ok_or(MoveError::Other)?;
22 let (piece, pieceless) = if start.is_ascii_lowercase() {
23 ('P', string)
24 } else {
25 (start, iter.as_str())
26 };
27 let mateless = pieceless.trim_end_matches('#');
28 let checkless = mateless.trim_end_matches('+');
29
30 let mates = pieceless.len() - mateless.len();
31 let checks = mateless.len() - checkless.len();
32
33 let (two_pos, promotion) = if let Some(equals) = checkless.find('=') {
34 let (left_over, promote) = checkless.split_at(equals);
35 let mut iter = promote.chars();
36 if iter.next() != Some('=') {
37 return Err(MoveError::Other);
38 }
39 let p = iter.next().ok_or(MoveError::Other)?;
40 if iter.next().is_some() {
41 return Err(MoveError::Other);
42 }
43 (left_over, Some(p))
44 } else {
45 (checkless, None)
46 };
47
48 let loc = if let Some(dash) = two_pos.find('-') {
49 dash
50 } else if let Some(x) = two_pos.find('x') {
51 x
52 } else {
53 return Err(MoveError::Other);
54 };
55 let (left, tmp) = two_pos.split_at(loc);
56 let (mid, mut right) = tmp.split_at(1); let from = left.parse::<Position>()?;
58 let captured = if mid == "x" {
59 let mut iter = right.chars();
60 let start = iter.next().ok_or(MoveError::Other)?;
61 Some(if start.is_ascii_lowercase() {
62 'P'
63 } else {
64 right = iter.as_str();
65 start
66 })
67 } else {
68 None
69 };
70 let to = right.parse::<Position>()?;
71 Ok(BasicMove {
72 piece,
73 from,
74 captured,
75 to,
76 promotion,
77 checks,
78 mates,
79 })
80 }
81}
82
83impl FromStr for Move {
84 type Err = MoveError;
85 fn from_str(string: &str) -> Result<Self, Self::Err> {
86 use Move::*;
87 Ok(match string {
88 "#" => Checkmate,
89 "S" => Stalemate,
90 "T" => Timeout,
91 "R" => Resign,
92 s if s.starts_with("O-O") => {
93 let mateless = s.trim_end_matches('#');
94 let mates = s.len() - mateless.len();
95 match mateless {
96 "O-O-O" => QueenCastle(mates),
97 "O-O" => KingCastle(mates),
98 _ => return Err(MoveError::Castle),
99 }
100 }
101 _ => Normal(string.parse::<BasicMove>()?),
102 })
103 }
104}
105
106struct MovePair {
107 main: Move,
108 modifier: Option<Move>,
109 stalemate: bool,
110}
111
112impl FromStr for MovePair {
113 type Err = MoveError;
114 fn from_str(string: &str) -> Result<Self, Self::Err> {
115 let mut stalemate = false;
116 let break_index = if string.len() == 2 {
117 1 } else if string.len() > 2 {
119 if string.ends_with("RS") && !string.ends_with("=RS")
120 || string.ends_with("TS") && !string.ends_with("=TS")
121 {
122 stalemate = true;
123 string.len() - 2
124 } else if (string.ends_with('R') && !string.ends_with("=R"))
125 || (string.ends_with('S') && !string.ends_with("=S"))
126 || (string.ends_with('T') && !string.ends_with("=T"))
127 {
128 string.len() - 1
129 } else {
130 0
131 }
132 } else {
133 0
134 };
135 Ok(if break_index == 0 {
136 Self {
137 main: string.parse()?,
138 modifier: None,
139 stalemate,
140 }
141 } else {
142 Self {
143 main: string.get(..break_index).ok_or(MoveError::Other)?.parse()?,
144 modifier: Some(
145 string
146 .get(break_index..(break_index + 1))
147 .ok_or(MoveError::Other)?
148 .parse()?,
149 ),
150 stalemate,
151 }
152 })
153 }
154}
155
156#[derive(PartialEq, Clone, Debug)]
157enum IntermediateError {
158 Other(usize),
159 TurnNumber(usize),
160 TurnNumberParse(usize, String),
161 TurnTooLong(usize),
162 MoveErr(MoveError, String, usize),
163 Description(usize),
164}
165
166fn parse_quarter(string: &str) -> Result<(QuarterTurn, &str), IntermediateError> {
167 fn next_move(c: char) -> bool {
171 c.is_whitespace()
172 || match c {
173 '.' | '{' | '(' | ')' => true,
174 _ => false,
175 }
176 }
177 use IntermediateError::*;
178 let trimmed = string.trim_start();
179 if trimmed == "" {
180 return Err(Other(trimmed.len()));
181 }
182 let split = trimmed.find(next_move).unwrap_or(string.len() - 1);
183 let (main_str, mut rest) = trimmed.split_at(split);
184 let move_pair = main_str
185 .trim()
186 .parse::<MovePair>()
187 .map_err(|m| MoveErr(m, main_str.to_owned(), rest.len()))?;
188 let mut description = None;
189 let mut alternatives = Vec::new();
190 rest = rest.trim_start();
191
192 if let Some(c) = rest.chars().next() {
193 if c == '{' {
194 let desc_end = rest.find('}').ok_or(Description(rest.len()))?;
195 let (mut desc_str, rest_tmp) = rest.split_at(desc_end + 1);
196 desc_str = desc_str.strip_prefix("{ ").ok_or(Description(rest.len()))?;
197 desc_str = desc_str.strip_suffix(" }").ok_or(Description(rest.len()))?;
198 description = Some(desc_str.to_owned());
199 rest = rest_tmp;
200 }
201 } else {
202 return Ok((
203 QuarterTurn {
204 main: move_pair.main,
205 modifier: move_pair.modifier,
206 extra_stalemate: move_pair.stalemate,
207 description,
208 alternatives,
209 },
210 rest,
211 ));
212 };
213
214 rest = rest.trim_start();
215
216 while let Some(rest_tmp) = rest.strip_prefix('(') {
217 rest = rest_tmp;
218 let mut turns = Vec::new();
219 while rest.chars().next() != Some(')') {
220 let (turn, rest_tmp) = parse_turn(rest)?;
221 rest = rest_tmp;
222 turns.push(turn);
223 }
224 rest = rest.strip_prefix(')').unwrap().trim_start();
225 alternatives.push(turns);
226 }
227 Ok((
228 QuarterTurn {
229 main: move_pair.main,
230 modifier: move_pair.modifier,
231 extra_stalemate: move_pair.stalemate,
232 description,
233 alternatives,
234 },
235 rest,
236 ))
237}
238
239fn parse_turn(string: &str) -> Result<(Turn, &str), IntermediateError> {
240 use IntermediateError::*;
241 let trimmed = string.trim_start();
242 let dot_loc = trimmed.find('.').ok_or(TurnNumber(trimmed.len()))?;
243 let (number_str, dots) = trimmed.split_at(dot_loc);
244 let number = if number_str == "" {
245 0
246 } else {
247 number_str
248 .parse()
249 .map_err(|_| TurnNumberParse(trimmed.len(), number_str.to_string()))?
250 };
251 let dot = dots.strip_prefix('.').unwrap();
252 let (mut rest, double_dot) = if let Some(dotted) = dot.strip_prefix('.') {
253 (dotted, true)
254 } else {
255 (dot, false)
256 };
257 let mut turns = Vec::new();
258 let for_error = rest.len();
259 let (qturn, rest_tmp) = parse_quarter(rest)?;
260 rest = rest_tmp.trim_start();
261 turns.push(qturn);
262 while let Some(rest_tmp) = rest.strip_prefix("..") {
263 if turns.len() >= 4 {
264 return Err(TurnTooLong(for_error));
265 }
266 let (qturn, rest_tmp) = parse_quarter(rest_tmp)?;
267 rest = rest_tmp.trim_start();
268 turns.push(qturn);
269 }
270 Ok((
271 Turn {
272 number,
273 double_dot,
274 turns,
275 },
276 rest,
277 ))
278}
279
280#[derive(Error, PartialEq, Clone, Debug)]
281pub enum PGN4Error {
282 #[error("Some error occured at {0}")]
283 Other(ErrorLocation),
284 #[error("Expected a turn number starting at {0}, but there isn't a dot")]
285 TurnNumber(ErrorLocation),
286 #[error("Turn number at {0} is malformed \"{1}\" should be a number or \"\"")]
287 TurnNumberParse(ErrorLocation, String),
288 #[error("More than 4 quarter turns are present in the turn starting at {0}")]
289 TurnTooLong(ErrorLocation),
290 #[error("Tag starting at {0} is malformed")]
291 BadTagged(ErrorLocation),
292 #[error("Move \"{1}\" at {2} failed to parse. {0}")]
293 BadMove(MoveError, String, ErrorLocation),
294 #[error("Description starting at {0} is malformed")]
295 BadDescription(ErrorLocation),
296}
297
298#[derive(PartialEq, Clone, Debug)]
299pub struct ErrorLocation {
300 pub line: usize,
301 pub column: usize,
302 pub raw_offset: usize,
303}
304
305impl std::fmt::Display for ErrorLocation {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 write!(f, "line {} column {}", self.line, self.column)
308 }
309}
310
311impl FromStr for PGN4 {
312 type Err = PGN4Error;
313 fn from_str(string: &str) -> Result<Self, Self::Err> {
314 let mut bracketed = Vec::new();
315 let mut rest = string;
316 while let Some(rest_tmp) = rest.strip_prefix('[') {
317 let label_end = rest_tmp.find(|c: char| c.is_whitespace()).unwrap_or(0);
318 let (label, middle) = rest_tmp.split_at(label_end);
319 rest = middle
320 .trim_start()
321 .strip_prefix('"')
322 .ok_or_else(|| make_tagged(rest_tmp, string))?;
323
324 let value_end = rest
325 .find('"')
326 .ok_or_else(|| make_tagged(rest_tmp, string))?;
327 let (value, end) = rest.split_at(value_end);
328 rest = end
329 .strip_prefix("\"]")
330 .ok_or_else(|| make_tagged(rest_tmp, string))?
331 .trim_start();
332
333 bracketed.push((label.to_owned(), value.to_owned()));
334 }
335 let mut turns = Vec::new();
336 while rest != "" {
337 let (turn, rest_tmp) = parse_turn(rest).map_err(|ie| add_details(ie, string))?;
338 rest = rest_tmp;
339 turns.push(turn);
340 }
341 Ok(PGN4 { bracketed, turns })
342 }
343}
344
345fn map_location(bytes_left: usize, base: &str) -> ErrorLocation {
346 let front = base.split_at(base.len() - bytes_left).0;
347 let from_last_newline = front.lines().last().unwrap();
348 let line = front.lines().count();
349 ErrorLocation {
350 line,
351 column: from_last_newline.chars().count(),
352 raw_offset: front.len(),
353 }
354}
355
356fn make_tagged(rest: &str, string: &str) -> PGN4Error {
357 PGN4Error::BadTagged(map_location(rest.len(), string))
358}
359
360fn add_details(ie: IntermediateError, string: &str) -> PGN4Error {
361 use IntermediateError::*;
362 match ie {
363 Other(r) => PGN4Error::Other(map_location(r, string)),
364 TurnNumber(r) => PGN4Error::TurnNumber(map_location(r, string)),
365 TurnNumberParse(r, num) => PGN4Error::TurnNumberParse(map_location(r, string), num),
366 TurnTooLong(r) => PGN4Error::TurnTooLong(map_location(r, string)),
367 MoveErr(m, e, r) => PGN4Error::BadMove(m, e, map_location(r, string)),
368 Description(r) => PGN4Error::BadDescription(map_location(r, string)),
369 }
370}