pgn_reader/
lib.rs

1//! A fast non-allocating and streaming reader for chess games in PGN notation.
2//!
3//! [`Reader`] parses games and calls methods of a user provided
4//! [`Visitor`]. Implementing custom visitors allows for maximum flexibility:
5//!
6//! * The reader itself does not allocate (besides a single fixed-size buffer).
7//!   The visitor can decide if and how to represent games in memory.
8//! * The reader does not validate move legality. This allows implementing
9//!   support for custom chess variants, or delaying move validation.
10//! * The visitor can short-circuit and let the reader use a fast path for
11//!   skipping games or variations.
12//!
13//! # Flow
14//!
15//! Visitor methods are called in these phases:
16//!
17//! 1. [`Visitor::begin_tags()`]
18//!    - [`Visitor::tag()`]
19//! 2. [`Visitor::begin_movetext()`]
20//!    - [`Visitor::san()`]
21//!    - [`Visitor::nag()`]
22//!    - [`Visitor::comment()`]
23//!    - [`Visitor::begin_variation()`] or skip
24//!    - [`Visitor::end_variation()`]
25//!    - [`Visitor::outcome()`]
26//! 3. [`Visitor::end_game()`]
27//!
28//! # Examples
29//!
30//! A visitor that counts the number of syntactically valid moves in mainline
31//! of each game.
32//!
33//! ```
34//! use std::{io, ops::ControlFlow};
35//! use pgn_reader::{Visitor, Skip, Reader, SanPlus};
36//!
37//! struct MoveCounter;
38//!
39//! impl Visitor for MoveCounter {
40//!     type Tags = ();
41//!     type Movetext = usize;
42//!     type Output = usize;
43//!
44//!     fn begin_tags(&mut self) -> ControlFlow<Self::Output, Self::Tags> {
45//!         ControlFlow::Continue(())
46//!     }
47//!
48//!     fn begin_movetext(&mut self, _tags: Self::Tags) -> ControlFlow<Self::Output, Self::Movetext> {
49//!         ControlFlow::Continue(0)
50//!     }
51//!
52//!     fn san(&mut self, movetext: &mut Self::Movetext, _san_plus: SanPlus) -> ControlFlow<Self::Output> {
53//!         *movetext += 1;
54//!         ControlFlow::Continue(())
55//!     }
56//!
57//!     fn begin_variation(&mut self, _movetext: &mut Self::Movetext) -> ControlFlow<Self::Output, Skip> {
58//!         ControlFlow::Continue(Skip(true)) // stay in the mainline
59//!     }
60//!
61//!     fn end_game(&mut self, movetext: Self::Movetext) -> Self::Output {
62//!         movetext
63//!     }
64//! }
65//!
66//! fn main() -> io::Result<()> {
67//!     let pgn = b"1. e4 e5 2. Nf3 (2. f4)
68//!                 { game paused due to bad weather }
69//!                 2... Nf6 *";
70//!
71//!     let mut reader = Reader::new(io::Cursor::new(&pgn));
72//!
73//!     let moves = reader.read_game(&mut MoveCounter)?;
74//!     assert_eq!(moves, Some(4));
75//!
76//!     Ok(())
77//! }
78//! ```
79//!
80//! A visitor that returns the final position using [`shakmaty`].
81//!
82//! ```
83//! use std::{error::Error, io, mem, ops::ControlFlow};
84//!
85//! use shakmaty::{CastlingMode, Chess, Position};
86//! use shakmaty::fen::Fen;
87//!
88//! use pgn_reader::{Visitor, Skip, RawTag, Reader, SanPlus};
89//!
90//! struct LastPosition;
91//!
92//! impl Visitor for LastPosition {
93//!     type Tags = Option<Chess>;
94//!     type Movetext = Chess;
95//!     type Output = Result<Chess, Box<dyn Error>>;
96//!
97//!     fn begin_tags(&mut self) -> ControlFlow<Self::Output, Self::Tags> {
98//!         ControlFlow::Continue(None)
99//!     }
100//!
101//!     fn tag(&mut self, tags: &mut Self::Tags, name: &[u8], value: RawTag<'_>) -> ControlFlow<Self::Output> {
102//!         // Support games from a non-standard starting position.
103//!         if name == b"FEN" {
104//!             let fen = match Fen::from_ascii(value.as_bytes()) {
105//!                 Ok(fen) => fen,
106//!                 Err(err) => return ControlFlow::Break(Err(err.into())),
107//!             };
108//!             let pos = match fen.into_position(CastlingMode::Standard) {
109//!                 Ok(pos) => pos,
110//!                 Err(err) => return ControlFlow::Break(Err(err.into())),
111//!             };
112//!             tags.replace(pos);
113//!         }
114//!         ControlFlow::Continue(())
115//!     }
116//!
117//!     fn begin_movetext(&mut self, tags: Self::Tags) -> ControlFlow<Self::Output, Self::Movetext> {
118//!         ControlFlow::Continue(tags.unwrap_or_default())
119//!     }
120//!
121//!     fn begin_variation(&mut self, _movetext: &mut Self::Movetext) -> ControlFlow<Self::Output, Skip> {
122//!         ControlFlow::Continue(Skip(true)) // stay in the mainline
123//!     }
124//!
125//!     fn san(&mut self, movetext: &mut Self::Movetext, san_plus: SanPlus) -> ControlFlow<Self::Output> {
126//!         match san_plus.san.to_move(movetext) {
127//!             Ok(m) => {
128//!                 movetext.play_unchecked(m);
129//!                 ControlFlow::Continue(())
130//!             }
131//!             Err(err) => ControlFlow::Break(Err(err.into()))
132//!         }
133//!     }
134//!
135//!     fn end_game(&mut self, movetext: Self::Movetext) -> Self::Output {
136//!         Ok(movetext)
137//!     }
138//! }
139//!
140//! fn main() -> io::Result<()> {
141//!     let pgn = b"1. f3 e5 2. g4 Qh4#";
142//!
143//!     let mut reader = Reader::new(io::Cursor::new(&pgn));
144//!
145//!     let pos = reader
146//!        .read_game(&mut LastPosition)?
147//!        .expect("game found")
148//!        .expect("valid and legal");
149//!
150//!     assert!(pos.is_checkmate());
151//!     Ok(())
152//! }
153//! ```
154
155#![warn(missing_debug_implementations)]
156
157mod buffer;
158pub mod comment;
159pub mod nag;
160pub mod reader;
161mod tag;
162mod visitor;
163
164pub use comment::RawComment;
165pub use nag::Nag;
166pub use reader::Reader;
167pub use shakmaty::{self, KnownOutcome, Outcome, san::SanPlus};
168pub use tag::RawTag;
169pub use visitor::{Skip, Visitor};