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};