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