1use std::{
2 borrow::Cow,
3 error::Error,
4 fmt,
5 str::{self, FromStr, Utf8Error},
6};
7
8#[derive(Clone, Eq, PartialEq, Debug)]
10#[must_use]
11pub struct Skip(pub bool);
12
13#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
15pub struct Nag(pub u8);
16
17impl Nag {
18 pub fn from_ascii(s: &[u8]) -> Result<Nag, InvalidNag> {
37 if s == b"?!" {
38 Ok(Nag::DUBIOUS_MOVE)
39 } else if s == b"?" {
40 Ok(Nag::MISTAKE)
41 } else if s == b"??" {
42 Ok(Nag::BLUNDER)
43 } else if s == b"!" {
44 Ok(Nag::GOOD_MOVE)
45 } else if s == b"!!" {
46 Ok(Nag::BRILLIANT_MOVE)
47 } else if s == b"!?" {
48 Ok(Nag::SPECULATIVE_MOVE)
49 } else if s.len() > 1 && s[0] == b'$' {
50 btoi::btou(&s[1..])
51 .ok()
52 .map(Nag)
53 .ok_or(InvalidNag { _priv: () })
54 } else {
55 Err(InvalidNag { _priv: () })
56 }
57 }
58
59 pub const GOOD_MOVE: Nag = Nag(1);
61
62 pub const MISTAKE: Nag = Nag(2);
64
65 pub const BRILLIANT_MOVE: Nag = Nag(3);
67
68 pub const BLUNDER: Nag = Nag(4);
70
71 pub const SPECULATIVE_MOVE: Nag = Nag(5);
73
74 pub const DUBIOUS_MOVE: Nag = Nag(6);
76}
77
78impl fmt::Display for Nag {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "${}", self.0)
81 }
82}
83
84impl From<u8> for Nag {
85 fn from(nag: u8) -> Nag {
86 Nag(nag)
87 }
88}
89
90#[derive(Clone, Eq, PartialEq)]
92pub struct InvalidNag {
93 _priv: (),
94}
95
96impl fmt::Debug for InvalidNag {
97 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98 f.debug_struct("InvalidNag").finish()
99 }
100}
101
102impl fmt::Display for InvalidNag {
103 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104 "invalid nag".fmt(f)
105 }
106}
107
108impl Error for InvalidNag {
109 fn description(&self) -> &str {
110 "invalid nag"
111 }
112}
113
114impl FromStr for Nag {
115 type Err = InvalidNag;
116
117 fn from_str(s: &str) -> Result<Nag, InvalidNag> {
118 Nag::from_ascii(s.as_bytes())
119 }
120}
121
122#[derive(Clone, Eq, PartialEq)]
132pub struct RawTag<'a>(pub &'a [u8]);
133
134impl<'a> RawTag<'a> {
135 pub fn as_bytes(&self) -> &[u8] {
137 self.0
138 }
139
140 pub fn decode(&self) -> Cow<'a, [u8]> {
143 let mut head = 0;
144 let mut decoded: Vec<u8> = Vec::new();
145 for escape in memchr::memchr_iter(b'\\', self.0) {
146 match self.0.get(escape + 1).cloned() {
147 Some(ch) if ch == b'\\' || ch == b'"' => {
148 decoded.extend_from_slice(&self.0[head..escape]);
149 head = escape + 1;
150 }
151 _ => (),
152 }
153 }
154 if head == 0 {
155 Cow::Borrowed(self.0)
156 } else {
157 decoded.extend_from_slice(&self.0[head..]);
158 Cow::Owned(decoded)
159 }
160 }
161
162 pub fn decode_utf8(&self) -> Result<Cow<'a, str>, Utf8Error> {
169 Ok(match self.decode() {
170 Cow::Borrowed(borrowed) => Cow::Borrowed(str::from_utf8(borrowed)?),
171 Cow::Owned(owned) => Cow::Owned(String::from_utf8(owned).map_err(|e| e.utf8_error())?),
172 })
173 }
174
175 pub fn decode_utf8_lossy(&self) -> Cow<'a, str> {
178 match self.decode() {
179 Cow::Borrowed(borrowed) => String::from_utf8_lossy(borrowed),
180 Cow::Owned(owned) => Cow::Owned(String::from_utf8_lossy(&owned).into_owned()),
181 }
182 }
183}
184
185impl<'a> fmt::Debug for RawTag<'a> {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 write!(f, "{:?}", self.decode_utf8_lossy())
188 }
189}
190
191#[derive(Clone, Eq, PartialEq)]
193pub struct RawComment<'a>(pub &'a [u8]);
194
195impl<'a> RawComment<'a> {
196 pub fn as_bytes(&self) -> &[u8] {
198 self.0
199 }
200}
201
202impl<'a> fmt::Debug for RawComment<'a> {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 write!(f, "{:?}", String::from_utf8_lossy(self.as_bytes()).as_ref())
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_nag() {
214 assert_eq!(Nag::from_ascii(b"$33"), Ok(Nag(33)));
215 }
216
217 #[test]
218 fn test_raw_tag() {
219 let tag = RawTag(b"Hello world");
220 assert_eq!(tag.decode().as_ref(), b"Hello world");
221
222 let tag = RawTag(b"Hello \\world\\");
223 assert_eq!(tag.decode().as_ref(), b"Hello \\world\\");
224
225 let tag = RawTag(b"\\Hello \\\"world\\\\");
226 assert_eq!(tag.decode().as_ref(), b"\\Hello \"world\\");
227 }
228}