pgn_reader/
nag.rs

1use std::{error::Error, fmt, str::FromStr};
2
3/// A numeric annotation glyph like `?`, `!!` or `$42`.
4#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
5pub struct Nag(pub u8);
6
7impl Nag {
8    /// Tries to parse a NAG from ASCII.
9    ///
10    /// # Examples
11    ///
12    /// ```
13    /// use pgn_reader::Nag;
14    ///
15    /// assert_eq!(Nag::from_ascii(b"??"), Ok(Nag(4)));
16    /// assert_eq!(Nag::from_ascii(b"$24"), Ok(Nag(24)));
17    /// ```
18    ///
19    /// # Errors
20    ///
21    /// Returns an [`InvalidNag`] error if the input is neither a known glyph
22    /// (`?!`, `!`, ...) nor a valid numeric annotation (`$0`, ..., `$255`).
23    pub fn from_ascii(s: &[u8]) -> Result<Nag, InvalidNag> {
24        if s == b"?!" {
25            Ok(Nag::DUBIOUS_MOVE)
26        } else if s == b"?" {
27            Ok(Nag::MISTAKE)
28        } else if s == b"??" {
29            Ok(Nag::BLUNDER)
30        } else if s == b"!" {
31            Ok(Nag::GOOD_MOVE)
32        } else if s == b"!!" {
33            Ok(Nag::BRILLIANT_MOVE)
34        } else if s == b"!?" {
35            Ok(Nag::SPECULATIVE_MOVE)
36        } else if s.len() > 1 && s[0] == b'$' {
37            btoi::btou(&s[1..]).ok().map(Nag).ok_or(InvalidNag)
38        } else {
39            Err(InvalidNag)
40        }
41    }
42
43    /// Appends the NAG to a string.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use pgn_reader::Nag;
49    ///
50    /// let mut buf = String::new();
51    /// Nag(255).append_to_string(&mut buf);
52    /// assert_eq!(buf, "$255");
53    /// ```
54    pub fn append_to_string(&self, s: &mut String) {
55        s.reserve(4);
56        s.push('$');
57        if self.0 >= 100 {
58            s.push((b'0' + (self.0 / 100) % 10) as char);
59        }
60        if self.0 >= 10 {
61            s.push((b'0' + (self.0 / 10) % 10) as char);
62        }
63        s.push((b'0' + (self.0 % 10)) as char);
64    }
65
66    /// Appends the NAG as ASCII to a byte buffer.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use pgn_reader::Nag;
72    ///
73    /// let mut buf = Vec::new();
74    /// Nag(255).append_ascii_to(&mut buf);
75    /// assert_eq!(buf, b"$255");
76    /// ```
77    pub fn append_ascii_to(&self, buf: &mut Vec<u8>) {
78        buf.reserve(4);
79        buf.push(b'$');
80        if self.0 >= 100 {
81            buf.push(b'0' + (self.0 / 100) % 10);
82        }
83        if self.0 >= 10 {
84            buf.push(b'0' + (self.0 / 10) % 10);
85        }
86        buf.push(b'0' + (self.0 % 10));
87    }
88
89    /// A good move (`!`).
90    pub const GOOD_MOVE: Nag = Nag(1);
91
92    /// A mistake (`?`).
93    pub const MISTAKE: Nag = Nag(2);
94
95    /// A brilliant move (`!!`).
96    pub const BRILLIANT_MOVE: Nag = Nag(3);
97
98    /// A blunder (`??`).
99    pub const BLUNDER: Nag = Nag(4);
100
101    /// A speculative move (`!?`).
102    pub const SPECULATIVE_MOVE: Nag = Nag(5);
103
104    /// A dubious move (`?!`).
105    pub const DUBIOUS_MOVE: Nag = Nag(6);
106}
107
108impl fmt::Display for Nag {
109    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110        write!(f, "${}", self.0)
111    }
112}
113
114impl From<u8> for Nag {
115    fn from(nag: u8) -> Nag {
116        Nag(nag)
117    }
118}
119
120impl From<Nag> for u8 {
121    fn from(Nag(nag): Nag) -> u8 {
122        nag
123    }
124}
125
126impl FromStr for Nag {
127    type Err = InvalidNag;
128
129    fn from_str(s: &str) -> Result<Nag, InvalidNag> {
130        Nag::from_ascii(s.as_bytes())
131    }
132}
133
134/// Error when parsing an invalid NAG.
135#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
136pub struct InvalidNag;
137
138impl fmt::Display for InvalidNag {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        f.write_str("invalid nag")
141    }
142}
143
144impl Error for InvalidNag {}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_from_ascii() {
152        assert_eq!(Nag::from_ascii(b"$1"), Ok(Nag(1)));
153        assert_eq!(Nag::from_ascii(b"$12"), Ok(Nag(12)));
154        assert_eq!(Nag::from_ascii(b"$123"), Ok(Nag(123)));
155        assert_eq!(Nag::from_ascii(b"$1234"), Err(InvalidNag));
156    }
157
158    #[test]
159    fn test_append_to_string() {
160        let mut s = String::new();
161        Nag(0).append_to_string(&mut s);
162        Nag(1).append_to_string(&mut s);
163        Nag(12).append_to_string(&mut s);
164        Nag(123).append_to_string(&mut s);
165        assert_eq!(s, "$0$1$12$123");
166    }
167
168    #[test]
169    fn test_append_ascii_to() {
170        let mut buf = Vec::new();
171        Nag(123).append_ascii_to(&mut buf);
172        Nag(12).append_ascii_to(&mut buf);
173        Nag(1).append_ascii_to(&mut buf);
174        Nag(0).append_ascii_to(&mut buf);
175        assert_eq!(buf, b"$123$12$1$0");
176    }
177}