Skip to main content

chess/
formats.rs

1use core::{error, fmt, str::FromStr};
2
3pub use std::collections::BTreeSet as Set;
4
5pub use winnow::ModalResult;
6pub use winnow::Parser;
7
8mod bits;
9
10pub mod chessbase;
11pub mod fen;
12pub mod pgn;
13pub mod san;
14pub mod text;
15pub mod uci;
16
17#[doc(inline)]
18pub use bits::Bits;
19
20#[doc(inline)]
21pub use fen::Fen;
22impl StrFormat for Fen {}
23
24#[doc(inline)]
25pub use pgn::Pgn;
26impl StrFormat for Pgn {}
27
28#[doc(inline)]
29pub use san::San;
30impl StrFormat for San {}
31
32#[doc(inline)]
33pub use text::Text;
34impl StrFormat for Text {}
35
36/// Slice of bytes.
37pub type ByteInput<'a> = &'a [u8];
38
39/// Slice of UTF-8 text.
40pub type StrInput<'a> = &'a str;
41
42/// Complete text format with parsing, formatting, and format-specific errors.
43pub trait StrFormat: fmt::Display + FromStr
44where
45    Self::Err: error::Error,
46{
47}
48
49pub(crate) mod prelude {
50    use std::io::{self, Read};
51
52    use super::ByteInput as Input;
53
54    pub use winnow::{
55        ModalResult,
56        Parser,
57        ascii::{dec_uint, multispace0, multispace1, space0, space1},
58        binary::{be_u16, be_u24, be_u32, i8, le_i32, le_u16, le_u24, le_u32, u8},
59        combinator::{
60            alt, backtrack_err, cut_err, delimited, opt, preceded, repeat, separated,
61            separated_pair, seq, terminated,
62        },
63        error::{ContextError, ErrMode, StrContext},
64        // stream::Bytes,
65        token::{any, none_of, one_of, take, take_till, take_while},
66    };
67
68    pub type ContextMode = ErrMode<ContextError>;
69
70    pub fn backtrack() -> ContextMode {
71        ErrMode::Backtrack(ContextError::new())
72    }
73
74    pub trait Framed {
75        const LEN: usize;
76
77        fn total(&self) -> usize;
78    }
79
80    pub enum FrameError<E> {
81        Error(E),
82        Io(io::Error),
83    }
84
85    pub fn framed<Header, T, H, P, F, E>(
86        input: &mut impl Read,
87        data: &mut Vec<u8>,
88        mut header: H,
89        parser: P,
90    ) -> core::result::Result<T, FrameError<E>>
91    where
92        Header: Framed,
93        H: for<'a> Parser<Input<'a>, Header, E>,
94        P: FnOnce(Header) -> F,
95        F: for<'a> Parser<Input<'a>, T, E>,
96    {
97        data.resize(Header::LEN, 0);
98        input.read_exact(data).map_err(FrameError::Io)?;
99        let mut prefix = data.as_slice();
100        let header = header.parse_next(&mut prefix).map_err(FrameError::Error)?;
101        data.resize(header.total(), 0);
102        input.read_exact(&mut data[Header::LEN..]).map_err(FrameError::Io)?;
103        let mut body = &data[Header::LEN..];
104        parser(header).parse_next(&mut body).map_err(FrameError::Error)
105    }
106}
107
108#[cfg(test)]
109pub mod twic {
110    pub fn data(issue: u16, suffix: &str) -> Vec<u8> {
111        let path = path(issue, suffix);
112        let error = |error| panic!("cannot read TWIC fixture {}: {error}", path.display());
113        std::fs::read(&path).unwrap_or_else(error)
114    }
115
116    pub fn path(issue: u16, suffix: &str) -> std::path::PathBuf {
117        std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("twic/twic{issue}.{suffix}"))
118    }
119}