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, 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 end_game(&mut self, movetext: Self::Movetext) -> Self::Output {
58//!         movetext
59//!     }
60//! }
61//!
62//! fn main() -> io::Result<()> {
63//!     let pgn = b"1. e4 e5 2. Nf3 (2. f4)
64//!                 { game paused due to bad weather }
65//!                 2... Nf6 *";
66//!
67//!     let mut reader = Reader::new(io::Cursor::new(&pgn));
68//!
69//!     let moves = reader.read_game(&mut MoveCounter)?;
70//!     assert_eq!(moves, Some(4));
71//!
72//!     Ok(())
73//! }
74//! ```
75//!
76//! A visitor that returns the final position using [`shakmaty`].
77//!
78//! ```
79//! use std::{error::Error, io, mem, ops::ControlFlow};
80//!
81//! use shakmaty::{CastlingMode, Chess, Position};
82//! use shakmaty::fen::Fen;
83//!
84//! use pgn_reader::{Visitor, RawTag, Reader, SanPlus};
85//!
86//! struct LastPosition;
87//!
88//! impl Visitor for LastPosition {
89//!     type Tags = Option<Chess>;
90//!     type Movetext = Chess;
91//!     type Output = Result<Chess, Box<dyn Error>>;
92//!
93//!     fn begin_tags(&mut self) -> ControlFlow<Self::Output, Self::Tags> {
94//!         ControlFlow::Continue(None)
95//!     }
96//!
97//!     fn tag(&mut self, tags: &mut Self::Tags, name: &[u8], value: RawTag<'_>) -> ControlFlow<Self::Output> {
98//!         // Support games from a non-standard starting position.
99//!         if name == b"FEN" {
100//!             let fen = match Fen::from_ascii(value.as_bytes()) {
101//!                 Ok(fen) => fen,
102//!                 Err(err) => return ControlFlow::Break(Err(err.into())),
103//!             };
104//!             let pos = match fen.into_position(CastlingMode::Standard) {
105//!                 Ok(pos) => pos,
106//!                 Err(err) => return ControlFlow::Break(Err(err.into())),
107//!             };
108//!             tags.replace(pos);
109//!         }
110//!         ControlFlow::Continue(())
111//!     }
112//!
113//!     fn begin_movetext(&mut self, tags: Self::Tags) -> ControlFlow<Self::Output, Self::Movetext> {
114//!         ControlFlow::Continue(tags.unwrap_or_default())
115//!     }
116//!
117//!     fn san(&mut self, movetext: &mut Self::Movetext, san_plus: SanPlus) -> ControlFlow<Self::Output> {
118//!         match san_plus.san.to_move(movetext) {
119//!             Ok(m) => {
120//!                 movetext.play_unchecked(m);
121//!                 ControlFlow::Continue(())
122//!             }
123//!             Err(err) => ControlFlow::Break(Err(err.into()))
124//!         }
125//!     }
126//!
127//!     fn end_game(&mut self, movetext: Self::Movetext) -> Self::Output {
128//!         Ok(movetext)
129//!     }
130//! }
131//!
132//! fn main() -> io::Result<()> {
133//!     let pgn = b"1. f3 e5 2. g4 Qh4#";
134//!
135//!     let mut reader = Reader::new(io::Cursor::new(&pgn));
136//!
137//!     let pos = reader
138//!        .read_game(&mut LastPosition)?
139//!        .expect("game found")
140//!        .expect("valid and legal");
141//!
142//!     assert!(pos.is_checkmate());
143//!     Ok(())
144//! }
145//! ```
146
147#![warn(missing_debug_implementations)]
148
149mod buffer;
150pub mod comment;
151pub mod nag;
152pub mod reader;
153mod tag;
154mod visitor;
155
156pub use comment::RawComment;
157pub use nag::Nag;
158pub use reader::Reader;
159pub use shakmaty::{self, KnownOutcome, Outcome, san::SanPlus};
160pub use tag::RawTag;
161pub use visitor::{Skip, Visitor};