From fdb2804883deb31e3aeb15bbe588dcc9b7b76bd0 Mon Sep 17 00:00:00 2001 From: Mica White Date: Mon, 8 Dec 2025 19:56:48 -0500 Subject: Stuff --- pdn/src/grammar.rs | 886 ++++++++++++++++++++++++++--------------------------- pdn/src/lib.rs | 0 pdn/src/tokens.rs | 568 +++++++++++++++++----------------- 3 files changed, 727 insertions(+), 727 deletions(-) mode change 100644 => 100755 pdn/src/grammar.rs mode change 100644 => 100755 pdn/src/lib.rs mode change 100644 => 100755 pdn/src/tokens.rs (limited to 'pdn/src') diff --git a/pdn/src/grammar.rs b/pdn/src/grammar.rs old mode 100644 new mode 100755 index 9529b59..ba3d086 --- a/pdn/src/grammar.rs +++ b/pdn/src/grammar.rs @@ -1,443 +1,443 @@ -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, - }) - } -} +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, + }) + } +} diff --git a/pdn/src/lib.rs b/pdn/src/lib.rs old mode 100644 new mode 100755 diff --git a/pdn/src/tokens.rs b/pdn/src/tokens.rs old mode 100644 new mode 100755 index d37d910..45e46e5 --- a/pdn/src/tokens.rs +++ b/pdn/src/tokens.rs @@ -1,284 +1,284 @@ -use std::sync::Arc; - -use snob::{csets, csets::CharacterSet, Scanner}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Color { - White, - Black, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum PdnTokenBody { - MoveNumber(usize, Color), - MoveSeparator, - CaptureSeparator, - AlphaSquare(char, char), - NumSquare(u8), - MoveStrength(Arc), - Nag(usize), - LeftParenthesis, - RightParenthesis, - LeftBracket, - RightBracket, - Asterisk, - Setup(Arc), - String(Arc), - Comment(Arc), - Identifier(Arc), - Space(Arc), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TokenHeader { - start: usize, - len: usize, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PdnToken { - pub header: TokenHeader, - pub body: PdnTokenBody, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TokenErrorType { - InvalidNumber(usize), - InvalidNag, - InvalidSquare, - UnterminatedSetup, - UnterminatedComment, - UnterminatedString, - InvalidToken, -} - -pub struct TokenError { - header: TokenHeader, - ty: TokenErrorType, -} - -pub struct PdnScanner { - scanner: Scanner, -} - -impl PdnScanner { - fn scan_string(&mut self) -> Option { - let mut string = String::new(); - loop { - if let Some(position) = self.scanner.many("\\\"".complement()) { - let part = self - .scanner - .goto(position) - .expect("position should be valid"); - string.push_str(&part); - } else if let Some(position) = self.scanner.starts_with("\\\"") { - self.scanner.goto(position); - string.push('"'); - } else { - break; - } - } - - if let Some(position) = self.scanner.any('"') { - self.scanner.goto(position); - Some(string) - } else { - None - } - } - - fn scan_unescaped_string(&mut self, terminator: char) -> Option { - let position = self.scanner.upto(terminator)?; - let string = self - .scanner - .goto(position) - .expect("position should be valid"); - let position = self - .scanner - .any(terminator) - .expect("there should be a terminator next"); - self.scanner.goto(position); - Some(string) - } - - fn scan_number(&mut self) -> Option { - let position = self.scanner.many(csets::AsciiDigits)?; - let number = self - .scanner - .goto(position) - .expect("position should be valid"); - let number: usize = number.parse().expect("should be a valid number"); - Some(number) - } - - fn scan_identifier(&mut self) -> Option { - let position = self - .scanner - .many(csets::AsciiLetters.union(csets::AsciiDigits).union('_'))?; - let identifier = self - .scanner - .goto(position) - .expect("position should be valid"); - Some(identifier) - } - - fn next_token(&mut self) -> Option> { - if self.scanner.is_at_end() { - return None; - } - - let token = if let Some(position) = self.scanner.any('-') { - self.scanner.goto(position); - Ok(PdnTokenBody::MoveSeparator) - } else if let Some(position) = self.scanner.any('x') { - self.scanner.goto(position); - Ok(PdnTokenBody::CaptureSeparator) - } else if let Some(position) = self.scanner.any('(') { - self.scanner.goto(position); - - // try a move strength token - if let Some(position) = self.scanner.many("?!") { - let char = self - .scanner - .char_at(position) - .expect("position should be valid"); - if char == ')' { - let strength = self - .scanner - .goto(position) - .expect("position should be valid"); - let position = self - .scanner - .any(')') - .expect("move strength should terminate"); - self.scanner.goto(position); - return Some(Ok(PdnTokenBody::MoveStrength(strength.into()))); - } - } - - Ok(PdnTokenBody::LeftParenthesis) - } else if let Some(position) = self.scanner.any(')') { - self.scanner.goto(position); - Ok(PdnTokenBody::RightParenthesis) - } else if let Some(position) = self.scanner.any('[') { - self.scanner.goto(position); - Ok(PdnTokenBody::LeftBracket) - } else if let Some(position) = self.scanner.any(']') { - self.scanner.goto(position); - Ok(PdnTokenBody::RightBracket) - } else if let Some(position) = self.scanner.any('*') { - self.scanner.goto(position); - Ok(PdnTokenBody::Asterisk) - } else if let Some(position) = self.scanner.any('$') { - self.scanner.goto(position); - match self.scan_number() { - Some(number) => Ok(PdnTokenBody::Nag(number)), - None => Err(TokenErrorType::InvalidNag), - } - } else if let Some(position) = self.scanner.any('/') { - self.scanner.goto(position); - match self.scan_unescaped_string('/') { - Some(string) => Ok(PdnTokenBody::Setup(string.into())), - None => Err(TokenErrorType::UnterminatedSetup), - } - } else if let Some(position) = self.scanner.any('{') { - self.scanner.goto(position); - match self.scan_unescaped_string('}') { - Some(string) => Ok(PdnTokenBody::Comment(string.into())), - None => Err(TokenErrorType::UnterminatedComment), - } - } else if let Some(position) = self.scanner.any('"') { - self.scanner.goto(position); - match self.scan_string() { - Some(string) => Ok(PdnTokenBody::String(string.into())), - None => Err(TokenErrorType::UnterminatedString), - } - } else if let Some(position) = self.scanner.many("?!") { - let strength = self - .scanner - .goto(position) - .expect("position should be valid"); - Ok(PdnTokenBody::MoveStrength(strength.into())) - } else if let Some(position) = self.scanner.any("abcdefgh") { - let letter = self - .scanner - .goto(position) - .expect("position should be valid") - .chars() - .next() - .expect("should contain one letter"); - if let Some(position) = self.scanner.any("12345678") { - let number = self - .scanner - .goto(position) - .expect("position should be valid") - .chars() - .next() - .expect("should contain one letter"); - Ok(PdnTokenBody::AlphaSquare(letter, number)) - } else { - self.scanner.advance(1); // skip over second character - Err(TokenErrorType::InvalidSquare) - } - } else if self.scanner.any(csets::AsciiUppercase).is_some() { - let identifier = self - .scan_identifier() - .expect("should be a valid identifier"); - Ok(PdnTokenBody::Identifier(identifier.into())) - } else if self.scanner.any(csets::AsciiDigits).is_some() { - let number = self.scan_number().expect("should be a valid number"); - if let Some(position) = self.scanner.starts_with("...") { - self.scanner.goto(position); - Ok(PdnTokenBody::MoveNumber(number, Color::Black)) - } else if let Some(position) = self.scanner.any('.') { - self.scanner.goto(position); - Ok(PdnTokenBody::MoveNumber(number, Color::White)) - } else if number < 100 { - Ok(PdnTokenBody::NumSquare(number as u8)) - } else { - Err(TokenErrorType::InvalidNumber(number)) - } - } else if let Some(position) = self.scanner.many(csets::AsciiWhitespace) { - let whitespace = self - .scanner - .goto(position) - .expect("position should be valid"); - Ok(PdnTokenBody::Space(whitespace.into())) - } else { - let position = self - .scanner - .upto(csets::AsciiLetters.union(csets::AsciiDigits.union("-x(?!)[]"))) - .unwrap_or_else(|| self.scanner.len()); - - self.scanner - .goto(position) - .expect("position should be valid"); - - Err(TokenErrorType::InvalidToken) - }; - - Some(token) - } -} - -impl Iterator for PdnScanner { - type Item = Result; - - fn next(&mut self) -> Option { - let start = self.scanner.position(); - let token = self.next_token()?; - let end = self.scanner.position(); - let len = end - start; - let header = TokenHeader { start, len }; - - let token = match token { - Ok(token) => Ok(PdnToken { - header, - body: token, - }), - Err(error) => Err(TokenError { header, ty: error }), - }; - - Some(token) - } -} +use std::sync::Arc; + +use snob::{csets, csets::CharacterSet, Scanner}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Color { + White, + Black, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PdnTokenBody { + MoveNumber(usize, Color), + MoveSeparator, + CaptureSeparator, + AlphaSquare(char, char), + NumSquare(u8), + MoveStrength(Arc), + Nag(usize), + LeftParenthesis, + RightParenthesis, + LeftBracket, + RightBracket, + Asterisk, + Setup(Arc), + String(Arc), + Comment(Arc), + Identifier(Arc), + Space(Arc), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TokenHeader { + start: usize, + len: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PdnToken { + pub header: TokenHeader, + pub body: PdnTokenBody, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TokenErrorType { + InvalidNumber(usize), + InvalidNag, + InvalidSquare, + UnterminatedSetup, + UnterminatedComment, + UnterminatedString, + InvalidToken, +} + +pub struct TokenError { + header: TokenHeader, + ty: TokenErrorType, +} + +pub struct PdnScanner { + scanner: Scanner, +} + +impl PdnScanner { + fn scan_string(&mut self) -> Option { + let mut string = String::new(); + loop { + if let Some(position) = self.scanner.many("\\\"".complement()) { + let part = self + .scanner + .goto(position) + .expect("position should be valid"); + string.push_str(&part); + } else if let Some(position) = self.scanner.starts_with("\\\"") { + self.scanner.goto(position); + string.push('"'); + } else { + break; + } + } + + if let Some(position) = self.scanner.any('"') { + self.scanner.goto(position); + Some(string) + } else { + None + } + } + + fn scan_unescaped_string(&mut self, terminator: char) -> Option { + let position = self.scanner.upto(terminator)?; + let string = self + .scanner + .goto(position) + .expect("position should be valid"); + let position = self + .scanner + .any(terminator) + .expect("there should be a terminator next"); + self.scanner.goto(position); + Some(string) + } + + fn scan_number(&mut self) -> Option { + let position = self.scanner.many(csets::AsciiDigits)?; + let number = self + .scanner + .goto(position) + .expect("position should be valid"); + let number: usize = number.parse().expect("should be a valid number"); + Some(number) + } + + fn scan_identifier(&mut self) -> Option { + let position = self + .scanner + .many(csets::AsciiLetters.union(csets::AsciiDigits).union('_'))?; + let identifier = self + .scanner + .goto(position) + .expect("position should be valid"); + Some(identifier) + } + + fn next_token(&mut self) -> Option> { + if self.scanner.is_at_end() { + return None; + } + + let token = if let Some(position) = self.scanner.any('-') { + self.scanner.goto(position); + Ok(PdnTokenBody::MoveSeparator) + } else if let Some(position) = self.scanner.any('x') { + self.scanner.goto(position); + Ok(PdnTokenBody::CaptureSeparator) + } else if let Some(position) = self.scanner.any('(') { + self.scanner.goto(position); + + // try a move strength token + if let Some(position) = self.scanner.many("?!") { + let char = self + .scanner + .char_at(position) + .expect("position should be valid"); + if char == ')' { + let strength = self + .scanner + .goto(position) + .expect("position should be valid"); + let position = self + .scanner + .any(')') + .expect("move strength should terminate"); + self.scanner.goto(position); + return Some(Ok(PdnTokenBody::MoveStrength(strength.into()))); + } + } + + Ok(PdnTokenBody::LeftParenthesis) + } else if let Some(position) = self.scanner.any(')') { + self.scanner.goto(position); + Ok(PdnTokenBody::RightParenthesis) + } else if let Some(position) = self.scanner.any('[') { + self.scanner.goto(position); + Ok(PdnTokenBody::LeftBracket) + } else if let Some(position) = self.scanner.any(']') { + self.scanner.goto(position); + Ok(PdnTokenBody::RightBracket) + } else if let Some(position) = self.scanner.any('*') { + self.scanner.goto(position); + Ok(PdnTokenBody::Asterisk) + } else if let Some(position) = self.scanner.any('$') { + self.scanner.goto(position); + match self.scan_number() { + Some(number) => Ok(PdnTokenBody::Nag(number)), + None => Err(TokenErrorType::InvalidNag), + } + } else if let Some(position) = self.scanner.any('/') { + self.scanner.goto(position); + match self.scan_unescaped_string('/') { + Some(string) => Ok(PdnTokenBody::Setup(string.into())), + None => Err(TokenErrorType::UnterminatedSetup), + } + } else if let Some(position) = self.scanner.any('{') { + self.scanner.goto(position); + match self.scan_unescaped_string('}') { + Some(string) => Ok(PdnTokenBody::Comment(string.into())), + None => Err(TokenErrorType::UnterminatedComment), + } + } else if let Some(position) = self.scanner.any('"') { + self.scanner.goto(position); + match self.scan_string() { + Some(string) => Ok(PdnTokenBody::String(string.into())), + None => Err(TokenErrorType::UnterminatedString), + } + } else if let Some(position) = self.scanner.many("?!") { + let strength = self + .scanner + .goto(position) + .expect("position should be valid"); + Ok(PdnTokenBody::MoveStrength(strength.into())) + } else if let Some(position) = self.scanner.any("abcdefgh") { + let letter = self + .scanner + .goto(position) + .expect("position should be valid") + .chars() + .next() + .expect("should contain one letter"); + if let Some(position) = self.scanner.any("12345678") { + let number = self + .scanner + .goto(position) + .expect("position should be valid") + .chars() + .next() + .expect("should contain one letter"); + Ok(PdnTokenBody::AlphaSquare(letter, number)) + } else { + self.scanner.advance(1); // skip over second character + Err(TokenErrorType::InvalidSquare) + } + } else if self.scanner.any(csets::AsciiUppercase).is_some() { + let identifier = self + .scan_identifier() + .expect("should be a valid identifier"); + Ok(PdnTokenBody::Identifier(identifier.into())) + } else if self.scanner.any(csets::AsciiDigits).is_some() { + let number = self.scan_number().expect("should be a valid number"); + if let Some(position) = self.scanner.starts_with("...") { + self.scanner.goto(position); + Ok(PdnTokenBody::MoveNumber(number, Color::Black)) + } else if let Some(position) = self.scanner.any('.') { + self.scanner.goto(position); + Ok(PdnTokenBody::MoveNumber(number, Color::White)) + } else if number < 100 { + Ok(PdnTokenBody::NumSquare(number as u8)) + } else { + Err(TokenErrorType::InvalidNumber(number)) + } + } else if let Some(position) = self.scanner.many(csets::AsciiWhitespace) { + let whitespace = self + .scanner + .goto(position) + .expect("position should be valid"); + Ok(PdnTokenBody::Space(whitespace.into())) + } else { + let position = self + .scanner + .upto(csets::AsciiLetters.union(csets::AsciiDigits.union("-x(?!)[]"))) + .unwrap_or_else(|| self.scanner.len()); + + self.scanner + .goto(position) + .expect("position should be valid"); + + Err(TokenErrorType::InvalidToken) + }; + + Some(token) + } +} + +impl Iterator for PdnScanner { + type Item = Result; + + fn next(&mut self) -> Option { + let start = self.scanner.position(); + let token = self.next_token()?; + let end = self.scanner.position(); + let len = end - start; + let header = TokenHeader { start, len }; + + let token = match token { + Ok(token) => Ok(PdnToken { + header, + body: token, + }), + Err(error) => Err(TokenError { header, ty: error }), + }; + + Some(token) + } +} -- cgit v1.2.3