use std::{iter::Peekable, sync::Arc}; use crate::tokens::{Color, PdnToken, PdnTokenBody, TokenHeader}; #[derive(Debug, Clone)] pub struct PdnFile { games: Vec, game_separators: Vec, } #[derive(Debug, Clone)] pub struct Game { header: Vec, body: Vec, } #[derive(Debug, Clone)] pub struct PdnTag { left_bracket: TokenHeader, identifier_token: TokenHeader, string_token: TokenHeader, right_bracket: TokenHeader, identifier: Arc, string: Arc, } #[derive(Debug, Clone)] pub enum BodyPart { Move(GameMove), Variation(Variation), Comment(TokenHeader, Arc), Setup(TokenHeader, Arc), Nag(TokenHeader, usize), } #[derive(Debug, Clone)] pub struct Variation { left_parenthesis: TokenHeader, body: Vec, right_parenthesis: TokenHeader, } #[derive(Debug, Clone)] pub struct GameMove { move_number: Option<(TokenHeader, usize, Color)>, game_move: Move, move_strength: Option<(TokenHeader, Arc)>, } #[derive(Debug, Clone)] pub enum Move { Normal(Square, TokenHeader, Square), Capture(Square, Vec<(TokenHeader, Square)>), } #[derive(Debug, Clone)] pub enum Square { Alpha(TokenHeader, char, char), Num(TokenHeader, u8), } /// Returns `Ok` if parsed successfully. If there are no tokens left, /// `Err(None)` is returned. If the next token is not a square position, then /// `Err(Some(token))` is returned. fn parse_square(scanner: &mut impl Iterator) -> Result> { let Some(token) = scanner.next() else { return Err(None); }; let header = token.header; let body = &token.body; match *body { PdnTokenBody::AlphaSquare(letter, number) => Ok(Square::Alpha(header, letter, number)), PdnTokenBody::NumSquare(number) => Ok(Square::Num(header, number)), _ => Err(Some(token)), } } #[derive(Debug, Clone)] pub enum MoveError { EndOfFile, NoStartSquare(Option), NoEndSquare(Option), InvalidCaptureSquares(Vec>), NoMoveSeparator, } fn parse_normal_move( first_square: Square, scanner: &mut impl Iterator, ) -> Result { let Some(separator) = scanner.next() else { return Err(MoveError::NoMoveSeparator); }; let square = match parse_square(scanner) { Ok(square) => square, Err(error) => return Err(MoveError::NoEndSquare(error)), }; Ok(Move::Normal(first_square, separator.header, square)) } fn parse_capture_move( first_square: Square, scanner: &mut Peekable>, ) -> Result { let mut captures = Vec::new(); let mut errors = Vec::new(); while let Some(token) = scanner.peek() { if token.body != PdnTokenBody::CaptureSeparator { break; } let separator = scanner.next().expect("separator should be next"); match parse_square(scanner) { Ok(square) => captures.push((separator.header, square)), Err(error) => errors.push(error), } } if !errors.is_empty() { Err(MoveError::InvalidCaptureSquares(errors)) } else { Ok(Move::Capture(first_square, captures)) } } fn parse_move(scanner: &mut Peekable>) -> Result { let square = match parse_square(scanner) { Ok(square) => square, Err(error) => return Err(MoveError::NoStartSquare(error)), }; let Some(token) = scanner.peek() else { return Err(MoveError::NoMoveSeparator); }; let body = &token.body; match body { PdnTokenBody::MoveSeparator => parse_normal_move(square, scanner), PdnTokenBody::CaptureSeparator => parse_capture_move(square, scanner), _ => Err(MoveError::NoMoveSeparator), } } #[derive(Debug, Clone)] pub enum GameMoveError { EndOfFile, BadMove(MoveError), } fn whitespace_if_found( scanner: &mut Peekable>, ) -> Option { let token = scanner.peek()?; if let PdnTokenBody::Space(_) = token.body { Some(scanner.next()?.header) } else { None } } fn parse_game_move( scanner: &mut Peekable>, ) -> Result { let Some(next_token) = scanner.peek() else { return Err(GameMoveError::EndOfFile); }; let move_number = match next_token.body { PdnTokenBody::MoveNumber(number, color) => Some((next_token.header, number, color)), _ => None, }; if move_number.is_some() { scanner.next(); } whitespace_if_found(scanner); let game_move = parse_move(scanner); let move_strength = if let Some(token) = scanner.peek() { if let PdnTokenBody::MoveStrength(string) = &token.body { Some((token.header, string.clone())) } else { None } } else { None }; if move_strength.is_some() { scanner.next(); } match game_move { Ok(game_move) => Ok(GameMove { move_number, game_move, move_strength, }), Err(error) => Err(GameMoveError::BadMove(error)), } } #[derive(Debug, Clone)] pub enum VariationError { UnexpectedEnd(BodyError), BadBody(BodyError), } fn parse_variation( scanner: &mut Peekable>, ) -> Result { let left_parenthesis = scanner.next().expect("should start with left paren").header; let body = parse_body_until(scanner, PdnTokenBody::RightParenthesis)?; let right_parenthesis = scanner.next().expect("should end with right paren").header; Ok(Variation { left_parenthesis, body, right_parenthesis, }) } #[derive(Debug, Clone)] pub enum BodyPartError { EndOfFile, InvalidToken(PdnToken), BadMove(GameMoveError), BadVariation(VariationError), } fn parse_body_part( scanner: &mut Peekable>, ) -> Result { let Some(token) = scanner.peek() else { return Err(BodyPartError::EndOfFile); }; match &token.body { PdnTokenBody::MoveNumber(..) | PdnTokenBody::AlphaSquare(..) | PdnTokenBody::NumSquare(..) => match parse_game_move(scanner) { Ok(mov) => Ok(BodyPart::Move(mov)), Err(error) => Err(BodyPartError::BadMove(error)), }, PdnTokenBody::LeftParenthesis => match parse_variation(scanner) { Ok(variation) => Ok(BodyPart::Variation(variation)), Err(error) => Err(BodyPartError::BadVariation(error)), }, PdnTokenBody::Comment(string) => Ok(BodyPart::Comment(token.header, string.clone())), PdnTokenBody::Setup(string) => Ok(BodyPart::Setup(token.header, string.clone())), PdnTokenBody::Nag(number) => Ok(BodyPart::Nag(token.header, *number)), _ => Err(BodyPartError::InvalidToken(token.clone())), } } pub type BodyError = Vec>; fn parse_body_until( scanner: &mut Peekable>, until: PdnTokenBody, ) -> Result, VariationError> { let mut parts = Vec::new(); loop { whitespace_if_found(scanner); let Some(token) = scanner.peek() else { return Err(VariationError::UnexpectedEnd(parts)); }; if token.body == until { break; } parts.push(parse_body_part(scanner)); whitespace_if_found(scanner); } if parts.iter().any(|r| r.is_err()) { Err(VariationError::BadBody(parts)) } else { Ok(parts.iter().map(|r| r.as_ref().cloned().unwrap()).collect()) } } #[derive(Debug, Clone)] pub enum PdnTagError { EndOfFile, NoStartBracket(PdnToken), Unterminated(Vec), NoIdentifier, NoString, NoEndBracket, } fn parse_pdn_tag( scanner: &mut Peekable>, ) -> Result { whitespace_if_found(scanner); let Some(left_bracket) = scanner.next() else { return Err(PdnTagError::EndOfFile); }; if left_bracket.body != PdnTokenBody::LeftBracket { return Err(PdnTagError::NoStartBracket(left_bracket)); } whitespace_if_found(scanner); let Some(identifier_token) = scanner.next() else { return Err(PdnTagError::Unterminated(vec![left_bracket])); }; let PdnTokenBody::Identifier(identifier) = &identifier_token.body else { return Err(PdnTagError::NoIdentifier); }; whitespace_if_found(scanner); let Some(value_token) = scanner.next() else { return Err(PdnTagError::Unterminated(vec![ left_bracket, identifier_token, ])); }; let PdnTokenBody::String(value) = &value_token.body else { return Err(PdnTagError::NoIdentifier); }; whitespace_if_found(scanner); let Some(right_bracket) = scanner.next() else { return Err(PdnTagError::Unterminated(vec![ left_bracket, identifier_token, value_token, ])); }; if right_bracket.body != PdnTokenBody::RightBracket { return Err(PdnTagError::NoEndBracket); } whitespace_if_found(scanner); Ok(PdnTag { left_bracket: left_bracket.header, identifier_token: identifier_token.header, string_token: value_token.header, right_bracket: right_bracket.header, identifier: identifier.clone(), string: value.clone(), }) } pub type HeaderError = Vec>; fn parse_header( scanner: &mut Peekable>, ) -> Result, HeaderError> { let mut tags = Vec::new(); loop { let Some(token) = scanner.peek() else { break; }; if token.body != PdnTokenBody::LeftBracket { break; } tags.push(parse_pdn_tag(scanner)); } if tags.iter().any(|r| r.is_err()) { Err(tags) } else { Ok(tags.iter().map(|r| r.as_ref().cloned().unwrap()).collect()) } } #[derive(Debug, Clone)] pub struct GameError { header: Result, HeaderError>, body: Result, VariationError>, } fn parse_game(scanner: &mut Peekable>) -> Result { let header = parse_header(scanner); let body = parse_body_until(scanner, PdnTokenBody::Asterisk); whitespace_if_found(scanner); if let Ok(header) = header { if let Ok(body) = body { Ok(Game { header, body }) } else { Err(GameError { header: Ok(header), body, }) } } else { Err(GameError { header, body }) } } pub type PdnError = Vec>; fn parse(scanner: &mut impl Iterator) -> Result { let mut scanner = scanner.peekable(); let mut games = Vec::new(); let mut game_separators = Vec::new(); loop { let Some(token) = scanner.peek() else { break; }; if token.body != PdnTokenBody::LeftBracket { break; } games.push(parse_game(&mut scanner)); game_separators.push(scanner.next().unwrap().header); } if games.iter().any(|r| r.is_err()) { Err(games) } else { let games = games.iter().map(|r| r.as_ref().cloned().unwrap()).collect(); Ok(PdnFile { games, game_separators, }) } }