From ed92bc6052c50b75d3124d58897dd0c0038a3fa1 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 21:09:17 +0200 Subject: [PATCH 01/10] introduce AppendAscii utility --- src/fen.rs | 89 +++++++++++++++++++++++++++++++-------------------- src/square.rs | 9 ++++-- src/types.rs | 11 ++++++- src/util.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 37 deletions(-) diff --git a/src/fen.rs b/src/fen.rs index f716919..042922d 100644 --- a/src/fen.rs +++ b/src/fen.rs @@ -50,22 +50,23 @@ //! ``` use core::{ - char, fmt, - fmt::{Display, Write as _}, + char, + fmt::{self, Display}, num::NonZeroU32, str::FromStr, }; +use crate::util::AppendAscii; use crate::{ Bitboard, Board, ByColor, ByRole, CastlingMode, Color, EnPassantMode, File, FromSetup, Piece, Position, PositionError, Rank, RemainingChecks, Role, Setup, Square, }; -fn fmt_castling( - f: &mut fmt::Formatter<'_>, +fn append_castling( + f: &mut W, board: &Board, castling_rights: Bitboard, -) -> fmt::Result { +) -> Result<(), W::Error> { let mut empty = true; for color in Color::ALL { @@ -76,7 +77,7 @@ fn fmt_castling( let candidates = board.by_piece(color.rook()) & color.backrank(); for rook in (castling_rights & color.backrank()).into_iter().rev() { - f.write_char( + f.append_ascii( if Some(rook) == candidates.first() && king.map_or(false, |k| rook < k) { color.fold_wb('Q', 'q') } else if Some(rook) == candidates.last() && king.map_or(false, |k| k < rook) { @@ -91,42 +92,45 @@ fn fmt_castling( } if empty { - f.write_char('-')?; + f.append_ascii('-')?; } Ok(()) } -fn fmt_pockets(f: &mut fmt::Formatter<'_>, pockets: &ByColor>) -> fmt::Result { - f.write_char('[')?; +fn append_pockets( + f: &mut W, + pockets: &ByColor>, +) -> Result<(), W::Error> { + f.append_ascii('[')?; for color in Color::ALL { for role in Role::ALL { let piece = Piece { color, role }; for _ in 0..*pockets.piece(piece) { - f.write_char(piece.char())?; + f.append_ascii(piece.char())?; } } } - f.write_char(']') + f.append_ascii(']') } -fn fmt_epd(f: &mut fmt::Formatter<'_>, setup: &Setup) -> fmt::Result { - setup.board.board_fen(setup.promoted).fmt(f)?; +fn append_epd(f: &mut W, setup: &Setup) -> Result<(), W::Error> { + setup.board.board_fen(setup.promoted).append_to(f)?; if let Some(ref pockets) = setup.pockets { - fmt_pockets(f, pockets)?; + append_pockets(f, pockets)?; } - f.write_char(' ')?; - f.write_char(setup.turn.char())?; - f.write_char(' ')?; - fmt_castling(f, &setup.board, setup.castling_rights)?; - f.write_char(' ')?; + f.append_ascii(' ')?; + f.append_ascii(setup.turn.char())?; + f.append_ascii(' ')?; + append_castling(f, &setup.board, setup.castling_rights)?; + f.append_ascii(' ')?; match setup.ep_square { - Some(ref ep_square) => Display::fmt(ep_square, f)?, - None => f.write_char('-')?, + Some(ref ep_square) => ep_square.append_to(f)?, + None => f.append_ascii('-')?, } if let Some(ref remaining_checks) = setup.remaining_checks { - f.write_char(' ')?; - Display::fmt(remaining_checks, f)?; + f.append_ascii(' ')?; + remaining_checks.append_to(f)?; } Ok(()) } @@ -272,7 +276,7 @@ impl FromStr for Board { impl Display for Board { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.board_fen(Bitboard(0)).fmt(f) + self.board_fen(Bitboard(0)).append_to(f) } } @@ -286,8 +290,8 @@ pub struct BoardFen<'b> { promoted: Bitboard, } -impl<'b> Display for BoardFen<'b> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl BoardFen<'_> { + fn append_to(&self, f: &mut W) -> Result<(), W::Error> { for rank in Rank::ALL.into_iter().rev() { let mut empty = 0; @@ -296,11 +300,11 @@ impl<'b> Display for BoardFen<'b> { empty = if let Some(piece) = self.board.piece_at(square) { if empty > 0 { - f.write_char(char::from_digit(empty, 10).expect("8 files only"))?; + f.append_ascii(char::from_digit(empty, 10).expect("8 files only"))?; } - f.write_char(piece.char())?; + f.append_ascii(piece.char())?; if self.promoted.contains(square) { - f.write_char('~')?; + f.append_ascii('~')?; } 0 } else { @@ -309,11 +313,11 @@ impl<'b> Display for BoardFen<'b> { } if empty > 0 { - f.write_char(char::from_digit(empty, 10).expect("8 files only"))?; + f.append_ascii(char::from_digit(empty, 10).expect("8 files only"))?; } if rank > Rank::First { - f.write_char('/')?; + f.append_ascii('/')?; } } @@ -321,6 +325,12 @@ impl<'b> Display for BoardFen<'b> { } } +impl Display for BoardFen<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.append_to(f) + } +} + /// A FEN like `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`. #[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct Fen(pub Setup); @@ -505,6 +515,14 @@ impl Fen { pub fn into_position(self, mode: CastlingMode) -> Result> { P::from_setup(self.0, mode) } + + fn append_to(&self, f: &mut W) -> Result<(), W::Error> { + append_epd(f, &self.0)?; + f.append_ascii(' ')?; + f.append_int(self.0.halfmoves)?; + f.append_ascii(' ')?; + f.append_int(u32::from(self.0.fullmoves)) + } } impl From for Fen { @@ -529,8 +547,7 @@ impl FromStr for Fen { impl Display for Fen { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_epd(f, &self.0)?; - write!(f, " {} {}", self.0.halfmoves, self.0.fullmoves) + self.append_to(f) } } @@ -571,6 +588,10 @@ impl Epd { pub fn into_position(self, mode: CastlingMode) -> Result> { P::from_setup(self.into_setup(), mode) } + + fn append_to(&self, f: &mut W) -> Result<(), W::Error> { + append_epd(f, &self.0) + } } impl From for Epd { @@ -595,7 +616,7 @@ impl FromStr for Epd { impl Display for Epd { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_epd(f, &self.0) + self.append_to(f) } } diff --git a/src/square.rs b/src/square.rs index 32c31fc..29ecd0a 100644 --- a/src/square.rs +++ b/src/square.rs @@ -8,6 +8,7 @@ use core::{ }; use crate::util::out_of_range_error; +use crate::util::AppendAscii; macro_rules! try_from_int_impl { ($type:ty, $lower:expr, $upper:expr, $($t:ty)+) => { @@ -601,6 +602,11 @@ impl Square { self.rank().distance(other.rank()), ) } + + pub(crate) fn append_to(self, f: &mut W) -> Result<(), W::Error> { + f.append_ascii(self.file().char())?; + f.append_ascii(self.rank().char()) + } } mod all_squares { @@ -650,8 +656,7 @@ impl str::FromStr for Square { impl fmt::Display for Square { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_char(self.file().char())?; - f.write_char(self.rank().char()) + self.append_to(f) } } diff --git a/src/types.rs b/src/types.rs index 33b1319..ab164ad 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use crate::util::AppendAscii; use core::{ fmt::{self, Display, Write as _}, num, @@ -370,8 +371,16 @@ macro_rules! try_remaining_checks_from_int_impl { try_remaining_checks_from_int_impl! { u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize } +impl ByColor { + pub(crate) fn append_to(self, f: &mut W) -> Result<(), W::Error> { + f.append_int(self.white.0)?; + f.append_ascii('+')?; + f.append_int(self.black.0) + } +} + impl Display for ByColor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}+{}", self.white.0, self.black.0) + self.append_to(f) } } diff --git a/src/util.rs b/src/util.rs index ee3175e..e832a81 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +use core::fmt; +use core::fmt::Write as _; use core::{convert::TryFrom as _, num::TryFromIntError}; pub(crate) fn out_of_range_error() -> TryFromIntError { @@ -18,3 +20,79 @@ macro_rules! from_enum_as_int_impl { })+ } } + +pub(crate) trait AppendAscii { + type Error; + + fn append_ascii(&mut self, ascii_char: char) -> Result<(), Self::Error>; + fn reserve(&mut self, additional: usize); + + fn append_int(&mut self, n: u32) -> Result<(), Self::Error> { + if n >= 1000_000_000 { + self.append_ascii(char::from(b'0' + ((n / 1000_000_000) % 10) as u8))?; + } + if n >= 100_000_000 { + self.append_ascii(char::from(b'0' + ((n / 100_000_000) % 10) as u8))?; + } + if n >= 10_000_000 { + self.append_ascii(char::from(b'0' + ((n / 10_000_000) % 10) as u8))?; + } + if n >= 1000_000 { + self.append_ascii(char::from(b'0' + ((n / 1000_000) % 10) as u8))?; + } + if n >= 100_000 { + self.append_ascii(char::from(b'0' + ((n / 100_000) % 10) as u8))?; + } + if n >= 10_000 { + self.append_ascii(char::from(b'0' + ((n / 10_000) % 10) as u8))?; + } + if n >= 1000 { + self.append_ascii(char::from(b'0' + ((n / 1000) % 10) as u8))?; + } + if n >= 100 { + self.append_ascii(char::from(b'0' + ((n / 100) % 10) as u8))?; + } + if n >= 10 { + self.append_ascii(char::from(b'0' + ((n / 10) % 10) as u8))?; + } + self.append_ascii(char::from(b'0' + (n % 10) as u8)) + } +} + +impl AppendAscii for fmt::Formatter<'_> { + type Error = fmt::Error; + + fn reserve(&mut self, _additional: usize) {} + + fn append_ascii(&mut self, ascii_char: char) -> Result<(), Self::Error> { + self.write_char(ascii_char) + } +} + +#[cfg(feature = "alloc")] +impl AppendAscii for alloc::string::String { + type Error = core::convert::Infallible; + + fn reserve(&mut self, additional: usize) { + self.reserve(additional); + } + + fn append_ascii(&mut self, ascii_char: char) -> Result<(), Self::Error> { + self.push(ascii_char); + Ok(()) + } +} + +#[cfg(feature = "alloc")] +impl AppendAscii for alloc::vec::Vec { + type Error = core::convert::Infallible; + + fn reserve(&mut self, additional: usize) { + self.reserve(additional); + } + + fn append_ascii(&mut self, ascii_char: char) -> Result<(), Self::Error> { + self.push(ascii_char as u8); + Ok(()) + } +} From 4448c4e0b49136087344356e7213fb66a1cf0aab Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 21:17:19 +0200 Subject: [PATCH 02/10] provide faster Fen::append_to_string() --- benches/benches.rs | 4 +++- src/fen.rs | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/benches/benches.rs b/benches/benches.rs index 32e27e3..8de87f6 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -102,10 +102,12 @@ fn bench_zobrist_hash() -> Zobrist64 { } fn bench_fen_roundtrip() -> String { + let mut buffer = String::new(); black_box("rnbqkb1r/1p3ppp/p2p1n2/4p3/3NP3/2N1B3/PPP2PPP/R2QKB1R w KQkq - 0 7") .parse::() .expect("valid fen") - .to_string() + .append_to_string(&mut buffer); + buffer } iai::main!( diff --git a/src/fen.rs b/src/fen.rs index 042922d..6947de0 100644 --- a/src/fen.rs +++ b/src/fen.rs @@ -523,6 +523,11 @@ impl Fen { f.append_ascii(' ')?; f.append_int(u32::from(self.0.fullmoves)) } + + #[cfg(feature = "alloc")] + pub fn append_to_string(self, s: &mut alloc::string::String) { + let _ = self.append_to(s); + } } impl From for Fen { From eadb91ff034a5ccbf02c2e104c08304551ba1d2a Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 21:48:56 +0200 Subject: [PATCH 03/10] allow skipping format machinery for fen writing --- src/fen.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++------ src/square.rs | 18 +++++++++++++++-- src/types.rs | 7 +++---- src/util.rs | 21 ++++++++++++++++---- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/src/fen.rs b/src/fen.rs index 6947de0..48e2abc 100644 --- a/src/fen.rs +++ b/src/fen.rs @@ -56,10 +56,9 @@ use core::{ str::FromStr, }; -use crate::util::AppendAscii; use crate::{ - Bitboard, Board, ByColor, ByRole, CastlingMode, Color, EnPassantMode, File, FromSetup, Piece, - Position, PositionError, Rank, RemainingChecks, Role, Setup, Square, + util::AppendAscii, Bitboard, Board, ByColor, ByRole, CastlingMode, Color, EnPassantMode, File, + FromSetup, Piece, Position, PositionError, Rank, RemainingChecks, Role, Setup, Square, }; fn append_castling( @@ -292,6 +291,8 @@ pub struct BoardFen<'b> { impl BoardFen<'_> { fn append_to(&self, f: &mut W) -> Result<(), W::Error> { + f.reserve(15); + for rank in Rank::ALL.into_iter().rev() { let mut empty = 0; @@ -323,6 +324,21 @@ impl BoardFen<'_> { Ok(()) } + + #[cfg(feature = "alloc")] + pub fn append_to_string(&self, s: &mut alloc::string::String) { + let _ = self.append_to(s); + } + + #[cfg(feature = "alloc")] + pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec) { + let _ = self.append_to(buf); + } + + #[cfg(feature = "std")] + pub fn write_ascii_to(&self, w: W) -> std::io::Result<()> { + self.append_to(&mut crate::util::WriteAscii(w)) + } } impl Display for BoardFen<'_> { @@ -519,15 +535,25 @@ impl Fen { fn append_to(&self, f: &mut W) -> Result<(), W::Error> { append_epd(f, &self.0)?; f.append_ascii(' ')?; - f.append_int(self.0.halfmoves)?; + f.append_u32(self.0.halfmoves)?; f.append_ascii(' ')?; - f.append_int(u32::from(self.0.fullmoves)) + f.append_u32(u32::from(self.0.fullmoves)) } #[cfg(feature = "alloc")] - pub fn append_to_string(self, s: &mut alloc::string::String) { + pub fn append_to_string(&self, s: &mut alloc::string::String) { let _ = self.append_to(s); } + + #[cfg(feature = "alloc")] + pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec) { + let _ = self.append_to(buf); + } + + #[cfg(feature = "std")] + pub fn write_ascii_to(&self, w: W) -> std::io::Result<()> { + self.append_to(&mut crate::util::WriteAscii(w)) + } } impl From for Fen { @@ -597,6 +623,21 @@ impl Epd { fn append_to(&self, f: &mut W) -> Result<(), W::Error> { append_epd(f, &self.0) } + + #[cfg(feature = "alloc")] + pub fn append_to_string(&self, s: &mut alloc::string::String) { + let _ = self.append_to(s); + } + + #[cfg(feature = "alloc")] + pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec) { + let _ = self.append_to(buf); + } + + #[cfg(feature = "std")] + pub fn write_ascii_to(&self, w: W) -> std::io::Result<()> { + self.append_to(&mut crate::util::WriteAscii(w)) + } } impl From for Epd { diff --git a/src/square.rs b/src/square.rs index 29ecd0a..a16c89b 100644 --- a/src/square.rs +++ b/src/square.rs @@ -7,8 +7,7 @@ use core::{ str, }; -use crate::util::out_of_range_error; -use crate::util::AppendAscii; +use crate::util::{out_of_range_error, AppendAscii}; macro_rules! try_from_int_impl { ($type:ty, $lower:expr, $upper:expr, $($t:ty)+) => { @@ -607,6 +606,21 @@ impl Square { f.append_ascii(self.file().char())?; f.append_ascii(self.rank().char()) } + + #[cfg(feature = "alloc")] + pub fn append_to_string(&self, s: &mut alloc::string::String) { + let _ = self.append_to(s); + } + + #[cfg(feature = "alloc")] + pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec) { + let _ = self.append_to(buf); + } + + #[cfg(feature = "std")] + pub fn write_ascii_to(&self, w: W) -> std::io::Result<()> { + self.append_to(&mut crate::util::WriteAscii(w)) + } } mod all_squares { diff --git a/src/types.rs b/src/types.rs index ab164ad..ba2a5ad 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,3 @@ -use crate::util::AppendAscii; use core::{ fmt::{self, Display, Write as _}, num, @@ -9,7 +8,7 @@ use crate::{ color::{ByColor, Color}, role::Role, square::Square, - util::out_of_range_error, + util::{out_of_range_error, AppendAscii}, }; /// A piece with [`Color`] and [`Role`]. @@ -373,9 +372,9 @@ try_remaining_checks_from_int_impl! { u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 us impl ByColor { pub(crate) fn append_to(self, f: &mut W) -> Result<(), W::Error> { - f.append_int(self.white.0)?; + f.append_u32(self.white.0)?; f.append_ascii('+')?; - f.append_int(self.black.0) + f.append_u32(self.black.0) } } diff --git a/src/util.rs b/src/util.rs index e832a81..4c83ed0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,4 @@ -use core::fmt; -use core::fmt::Write as _; -use core::{convert::TryFrom as _, num::TryFromIntError}; +use core::{convert::TryFrom as _, fmt, fmt::Write as _, num::TryFromIntError}; pub(crate) fn out_of_range_error() -> TryFromIntError { // This is a hack to construct TryFromIntError despite its private @@ -27,7 +25,7 @@ pub(crate) trait AppendAscii { fn append_ascii(&mut self, ascii_char: char) -> Result<(), Self::Error>; fn reserve(&mut self, additional: usize); - fn append_int(&mut self, n: u32) -> Result<(), Self::Error> { + fn append_u32(&mut self, n: u32) -> Result<(), Self::Error> { if n >= 1000_000_000 { self.append_ascii(char::from(b'0' + ((n / 1000_000_000) % 10) as u8))?; } @@ -96,3 +94,18 @@ impl AppendAscii for alloc::vec::Vec { Ok(()) } } + +#[cfg(feature = "std")] +pub(crate) struct WriteAscii(pub W); + +#[cfg(feature = "std")] +impl AppendAscii for WriteAscii { + type Error = std::io::Error; + + fn reserve(&mut self, _additional: usize) {} + + fn append_ascii(&mut self, ascii_char: char) -> Result<(), Self::Error> { + let buf = [ascii_char as u8]; + self.0.write_all(&buf[..]) + } +} From 72ab6408e7e5ff28b24bf078065aeb04e557766a Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 21:58:16 +0200 Subject: [PATCH 04/10] implement UciMove writing without format machinery --- src/uci.rs | 60 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/src/uci.rs b/src/uci.rs index ac1784b..0fd8de4 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -66,7 +66,7 @@ use core::{fmt, str::FromStr}; -use crate::{CastlingMode, CastlingSide, Move, Position, Rank, Role, Square}; +use crate::{util::AppendAscii, CastlingMode, CastlingSide, Move, Position, Rank, Role, Square}; /// Error when parsing an invalid UCI move. #[derive(Clone, Debug)] @@ -128,20 +128,7 @@ impl FromStr for UciMove { impl fmt::Display for UciMove { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - UciMove::Normal { - from, - to, - promotion: None, - } => write!(f, "{from}{to}"), - UciMove::Normal { - from, - to, - promotion: Some(promotion), - } => write!(f, "{}{}{}", from, to, promotion.char()), - UciMove::Put { to, role } => write!(f, "{}@{}", role.upper_char(), to), - UciMove::Null => f.write_str("0000"), - } + self.append_to(f) } } @@ -353,6 +340,49 @@ impl UciMove { Err(IllegalUciMoveError) } } + + fn append_to(&self, f: &mut W) -> Result<(), W::Error> { + match *self { + UciMove::Normal { + from, + to, + promotion, + } => { + from.append_to(f)?; + to.append_to(f)?; + if let Some(promotion) = promotion { + f.append_ascii(promotion.char())?; + } + } + UciMove::Put { role, to } => { + f.append_ascii(role.upper_char())?; + f.append_ascii('@')?; + to.append_to(f)?; + } + UciMove::Null => { + f.append_ascii('0')?; + f.append_ascii('0')?; + f.append_ascii('0')?; + f.append_ascii('0')?; + } + } + Ok(()) + } + + #[cfg(feature = "alloc")] + pub fn append_to_string(&self, s: &mut alloc::string::String) { + let _ = self.append_to(s); + } + + #[cfg(feature = "alloc")] + pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec) { + let _ = self.append_to(buf); + } + + #[cfg(feature = "std")] + pub fn write_ascii_to(&self, w: W) -> std::io::Result<()> { + self.append_to(&mut crate::util::WriteAscii(w)) + } } impl Move { From 0ead87bca436e608fefe338ac9a0b9c66efa2d46 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 22:02:11 +0200 Subject: [PATCH 05/10] add Outcome::as_str() --- src/position.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/position.rs b/src/position.rs index 6b1ae39..126ad89 100644 --- a/src/position.rs +++ b/src/position.rs @@ -47,15 +47,19 @@ impl Outcome { _ => return Err(ParseOutcomeError::Invalid), }) } -} -impl fmt::Display for Outcome { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { + pub const fn as_str(self) -> &'static str { + match self { Outcome::Decisive { winner: White } => "1-0", Outcome::Decisive { winner: Black } => "0-1", Outcome::Draw => "1/2-1/2", - }) + } + } +} + +impl fmt::Display for Outcome { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) } } From 72c3d50655c0e4c4991b312dad4489278eacd84a Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 22:18:39 +0200 Subject: [PATCH 06/10] implement san writing without format machinery --- src/san.rs | 120 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/src/san.rs b/src/san.rs index e2b7f51..5aea388 100644 --- a/src/san.rs +++ b/src/san.rs @@ -54,7 +54,9 @@ use core::{fmt, str::FromStr}; -use crate::{CastlingSide, File, Move, MoveList, Outcome, Position, Rank, Role, Square}; +use crate::{ + util::AppendAscii, CastlingSide, File, Move, MoveList, Outcome, Position, Rank, Role, Square, +}; /// Error when parsing a syntactically invalid SAN. #[derive(Clone, Debug)] @@ -455,18 +457,8 @@ impl San { San::Null => false, } } -} - -impl FromStr for San { - type Err = ParseSanError; - fn from_str(san: &str) -> Result { - San::from_ascii(san.as_bytes()) - } -} - -impl fmt::Display for San { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn append_to(&self, f: &mut W) -> Result<(), W::Error> { match *self { San::Normal { role, @@ -477,32 +469,77 @@ impl fmt::Display for San { promotion, } => { if role != Role::Pawn { - write!(f, "{}", role.upper_char())?; + f.append_ascii(role.upper_char())?; } if let Some(file) = file { - write!(f, "{}", file.char())?; + f.append_ascii(file.char())?; } if let Some(rank) = rank { - write!(f, "{}", rank.char())?; + f.append_ascii(rank.char())?; } if capture { - write!(f, "x")?; + f.append_ascii('x')?; } - write!(f, "{to}")?; + to.append_to(f)?; if let Some(promotion) = promotion { - write!(f, "={}", promotion.upper_char())?; + f.append_ascii('=')?; + f.append_ascii(promotion.upper_char())?; } - Ok(()) } - San::Castle(CastlingSide::KingSide) => write!(f, "O-O"), - San::Castle(CastlingSide::QueenSide) => write!(f, "O-O-O"), - San::Put { - role: Role::Pawn, - to, - } => write!(f, "@{to}"), - San::Put { role, to } => write!(f, "{}@{}", role.upper_char(), to), - San::Null => write!(f, "--"), + San::Castle(CastlingSide::KingSide) => { + f.append_ascii('O')?; + f.append_ascii('-')?; + f.append_ascii('O')?; + } + San::Castle(CastlingSide::QueenSide) => { + f.append_ascii('O')?; + f.append_ascii('-')?; + f.append_ascii('O')?; + f.append_ascii('-')?; + f.append_ascii('O')?; + } + San::Put { role, to } => { + if role != Role::Pawn { + f.append_ascii(role.upper_char())?; + } + f.append_ascii('@')?; + to.append_to(f)?; + } + San::Null => { + f.append_ascii('-')?; + f.append_ascii('-')?; + } } + Ok(()) + } + + #[cfg(feature = "alloc")] + pub fn append_to_string(&self, s: &mut alloc::string::String) { + let _ = self.append_to(s); + } + + #[cfg(feature = "alloc")] + pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec) { + let _ = self.append_to(buf); + } + + #[cfg(feature = "std")] + pub fn write_ascii_to(&self, w: W) -> std::io::Result<()> { + self.append_to(&mut crate::util::WriteAscii(w)) + } +} + +impl FromStr for San { + type Err = ParseSanError; + + fn from_str(san: &str) -> Result { + San::from_ascii(san.as_bytes()) + } +} + +impl fmt::Display for San { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.append_to(f) } } @@ -606,6 +643,29 @@ impl SanPlus { }, } } + + fn append_to(&self, f: &mut W) -> Result<(), W::Error> { + self.san.append_to(f)?; + if let Some(suffix) = self.suffix { + f.append_ascii(suffix.char())?; + } + Ok(()) + } + + #[cfg(feature = "alloc")] + pub fn append_to_string(&self, s: &mut alloc::string::String) { + let _ = self.append_to(s); + } + + #[cfg(feature = "alloc")] + pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec) { + let _ = self.append_to(buf); + } + + #[cfg(feature = "std")] + pub fn write_ascii_to(&self, w: W) -> std::io::Result<()> { + self.append_to(&mut crate::util::WriteAscii(w)) + } } impl FromStr for SanPlus { @@ -618,11 +678,7 @@ impl FromStr for SanPlus { impl fmt::Display for SanPlus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.san)?; - if let Some(suffix) = self.suffix { - write!(f, "{suffix}")?; - } - Ok(()) + self.append_to(f) } } From 21098f551c3e17e27980bb515425029d8d0736ba Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 22:38:41 +0200 Subject: [PATCH 07/10] add once more strategic reserve --- src/fen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fen.rs b/src/fen.rs index 48e2abc..42ce479 100644 --- a/src/fen.rs +++ b/src/fen.rs @@ -114,6 +114,7 @@ fn append_pockets( } fn append_epd(f: &mut W, setup: &Setup) -> Result<(), W::Error> { + f.reserve(21); setup.board.board_fen(setup.promoted).append_to(f)?; if let Some(ref pockets) = setup.pockets { append_pockets(f, pockets)?; From 1251663e005fbd96dc4f569c6396e66dada39811 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 23:14:28 +0200 Subject: [PATCH 08/10] skip empty squares more quickly in fen writing --- src/fen.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/fen.rs b/src/fen.rs index 42ce479..6c37ca9 100644 --- a/src/fen.rs +++ b/src/fen.rs @@ -295,27 +295,24 @@ impl BoardFen<'_> { f.reserve(15); for rank in Rank::ALL.into_iter().rev() { - let mut empty = 0; + let mut prev_file = -1; - for file in File::ALL { - let square = Square::from_coords(file, rank); + for square in self.board.occupied() & rank { + let empty = i32::from(square.file()) - prev_file - 1; + if empty > 0 { + f.append_ascii(char::from(b'0' + empty as u8))?; + } + prev_file = i32::from(square.file()); - empty = if let Some(piece) = self.board.piece_at(square) { - if empty > 0 { - f.append_ascii(char::from_digit(empty, 10).expect("8 files only"))?; - } - f.append_ascii(piece.char())?; - if self.promoted.contains(square) { - f.append_ascii('~')?; - } - 0 - } else { - empty + 1 - }; + f.append_ascii(self.board.piece_at(square).expect("piece").char())?; + if self.promoted.contains(square) { + f.append_ascii('~')?; + } } + let empty = i32::from(File::H) - prev_file; if empty > 0 { - f.append_ascii(char::from_digit(empty, 10).expect("8 files only"))?; + f.append_ascii(char::from(b'0' + empty as u8))?; } if rank > Rank::First { From 219693a2abe9d5974b407e4dd90118691c00c7de Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 23:31:04 +0200 Subject: [PATCH 09/10] make clippy happy --- src/util.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/util.rs b/src/util.rs index 4c83ed0..e893285 100644 --- a/src/util.rs +++ b/src/util.rs @@ -26,8 +26,8 @@ pub(crate) trait AppendAscii { fn reserve(&mut self, additional: usize); fn append_u32(&mut self, n: u32) -> Result<(), Self::Error> { - if n >= 1000_000_000 { - self.append_ascii(char::from(b'0' + ((n / 1000_000_000) % 10) as u8))?; + if n >= 1_000_000_000 { + self.append_ascii(char::from(b'0' + ((n / 1_000_000_000) % 10) as u8))?; } if n >= 100_000_000 { self.append_ascii(char::from(b'0' + ((n / 100_000_000) % 10) as u8))?; @@ -35,8 +35,8 @@ pub(crate) trait AppendAscii { if n >= 10_000_000 { self.append_ascii(char::from(b'0' + ((n / 10_000_000) % 10) as u8))?; } - if n >= 1000_000 { - self.append_ascii(char::from(b'0' + ((n / 1000_000) % 10) as u8))?; + if n >= 1_000_000 { + self.append_ascii(char::from(b'0' + ((n / 1_000_000) % 10) as u8))?; } if n >= 100_000 { self.append_ascii(char::from(b'0' + ((n / 100_000) % 10) as u8))?; @@ -44,8 +44,8 @@ pub(crate) trait AppendAscii { if n >= 10_000 { self.append_ascii(char::from(b'0' + ((n / 10_000) % 10) as u8))?; } - if n >= 1000 { - self.append_ascii(char::from(b'0' + ((n / 1000) % 10) as u8))?; + if n >= 1_000 { + self.append_ascii(char::from(b'0' + ((n / 1_000) % 10) as u8))?; } if n >= 100 { self.append_ascii(char::from(b'0' + ((n / 100) % 10) as u8))?; From 18cc6530b2e025984a78c9910daaa0d3bacd16a3 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 31 Aug 2024 23:43:58 +0200 Subject: [PATCH 10/10] bench now requires std --- .github/workflows/test.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2eb223e..a74970d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,14 +25,24 @@ jobs: flags: "-Z minimal-versions --all-features" runs-on: ubuntu-latest steps: - - run: sudo apt-get update && sudo apt-get install -y valgrind - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - run: cargo test --no-default-features ${{ matrix.flags }} - run: cargo doc --no-default-features ${{ matrix.flags }} - - run: cargo bench --no-default-features ${{ matrix.flags }} + bench: + runs-on: ubuntu-latest + steps: + - run: sudo apt-get update && sudo apt-get install -y valgrind + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo bench + check_fuzz: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly - run: cargo check --manifest-path fuzz/Cargo.toml check_no_std: runs-on: ubuntu-latest