hayro_syntax/object/
name.rs

1//! Names.
2
3use crate::object::Object;
4use crate::object::macros::object;
5use crate::reader::Reader;
6use crate::reader::{Readable, ReaderContext, Skippable};
7use crate::trivia::is_regular_character;
8use std::fmt::Debug;
9use std::hash::Hash;
10use std::ops::Deref;
11
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13enum Cow<'a> {
14    Borrowed(&'a [u8]),
15    Owned(Vec<u8>),
16}
17
18/// A name.
19#[derive(Debug, Clone)]
20pub struct Name<'a>(Cow<'a>);
21
22// Two names should be equal even if one is borrowed and the other is owned,
23// so we need these manual implementations.
24
25impl<'a> Hash for Name<'a> {
26    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
27        self.deref().hash(state);
28    }
29}
30
31impl PartialEq for Name<'_> {
32    fn eq(&self, other: &Self) -> bool {
33        self.deref() == other.deref()
34    }
35}
36
37impl Eq for Name<'_> {}
38
39#[allow(clippy::non_canonical_partial_ord_impl)]
40impl PartialOrd for Name<'_> {
41    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
42        self.deref().partial_cmp(other.deref())
43    }
44}
45
46impl Ord for Name<'_> {
47    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
48        self.deref().cmp(other.deref())
49    }
50}
51
52impl<'a> AsRef<Self> for Name<'a> {
53    fn as_ref(&self) -> &Self {
54        self
55    }
56}
57
58impl Deref for Name<'_> {
59    type Target = [u8];
60
61    fn deref(&self) -> &Self::Target {
62        match &self.0 {
63            Cow::Borrowed(a) => a,
64            Cow::Owned(v) => v.as_ref(),
65        }
66    }
67}
68
69impl<'a> Name<'a> {
70    /// Create a new name from a sequence of bytes.
71    pub fn new(data: &'a [u8]) -> Self {
72        fn convert_hex(c: u8) -> u8 {
73            match c {
74                b'A'..=b'F' => c - b'A' + 10,
75                b'a'..=b'f' => c - b'a' + 10,
76                b'0'..=b'9' => c - b'0',
77                _ => unreachable!(),
78            }
79        }
80
81        let data = if !data.contains(&b'#') {
82            Cow::Borrowed(data)
83        } else {
84            let mut cleaned = vec![];
85
86            let mut r = Reader::new(data);
87
88            while let Some(b) = r.read_byte() {
89                if b == b'#' {
90                    // We already verified when skipping that it's a valid hex sequence.
91                    let hex = r.read_bytes(2).unwrap();
92                    cleaned.push(convert_hex(hex[0]) << 4 | convert_hex(hex[1]));
93                } else {
94                    cleaned.push(b);
95                }
96            }
97
98            Cow::Owned(cleaned)
99        };
100
101        Self(data)
102    }
103
104    /// Create a new name from an unescaped bytes string.
105    pub(crate) const fn from_unescaped(data: &'a [u8]) -> Self {
106        Self(Cow::Borrowed(data))
107    }
108
109    /// Return a string representation of the name.
110    pub fn as_str(&self) -> &str {
111        std::str::from_utf8(self.deref()).unwrap_or("{non-ascii key}")
112    }
113}
114
115object!(Name<'a>, Name);
116
117impl Skippable for Name<'_> {
118    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
119        skip_name_like(r, true).map(|_| ())
120    }
121}
122
123impl<'a> Readable<'a> for Name<'a> {
124    fn read(r: &mut Reader<'a>, _: &ReaderContext<'_>) -> Option<Self> {
125        let data = {
126            let start = r.offset();
127            skip_name_like(r, true)?;
128            let end = r.offset();
129
130            r.range(start + 1..end).unwrap()
131        };
132
133        Some(Self::new(data))
134    }
135}
136
137// This method is shared by `Name` and the parser for content stream operators (which behave like
138// names, except that they aren't preceded by a solidus.
139pub(crate) fn skip_name_like(r: &mut Reader<'_>, solidus: bool) -> Option<()> {
140    if solidus {
141        r.forward_tag(b"/")?;
142    }
143
144    let old = r.offset();
145
146    while let Some(b) = r.eat(is_regular_character) {
147        if b == b'#' {
148            r.eat(|n| n.is_ascii_hexdigit())?;
149            r.eat(|n| n.is_ascii_hexdigit())?;
150        }
151    }
152
153    if !solidus && old == r.offset() {
154        return None;
155    }
156
157    Some(())
158}
159
160#[cfg(test)]
161mod tests {
162    use crate::object::Name;
163    use crate::reader::Reader;
164    use crate::reader::ReaderExt;
165    use std::ops::Deref;
166
167    #[test]
168    fn name_1() {
169        assert_eq!(
170            Reader::new("/".as_bytes())
171                .read_without_context::<Name<'_>>()
172                .unwrap()
173                .deref(),
174            b""
175        );
176    }
177
178    #[test]
179    fn name_2() {
180        assert!(
181            Reader::new("dfg".as_bytes())
182                .read_without_context::<Name<'_>>()
183                .is_none()
184        );
185    }
186
187    #[test]
188    fn name_3() {
189        assert!(
190            Reader::new("/AB#FG".as_bytes())
191                .read_without_context::<Name<'_>>()
192                .is_none()
193        );
194    }
195
196    #[test]
197    fn name_4() {
198        assert_eq!(
199            Reader::new("/Name1".as_bytes())
200                .read_without_context::<Name<'_>>()
201                .unwrap()
202                .deref(),
203            b"Name1"
204        );
205    }
206
207    #[test]
208    fn name_5() {
209        assert_eq!(
210            Reader::new("/ASomewhatLongerName".as_bytes())
211                .read_without_context::<Name<'_>>()
212                .unwrap()
213                .deref(),
214            b"ASomewhatLongerName"
215        );
216    }
217
218    #[test]
219    fn name_6() {
220        assert_eq!(
221            Reader::new("/A;Name_With-Various***Characters?".as_bytes())
222                .read_without_context::<Name<'_>>()
223                .unwrap()
224                .deref(),
225            b"A;Name_With-Various***Characters?"
226        );
227    }
228
229    #[test]
230    fn name_7() {
231        assert_eq!(
232            Reader::new("/1.2".as_bytes())
233                .read_without_context::<Name<'_>>()
234                .unwrap()
235                .deref(),
236            b"1.2"
237        );
238    }
239
240    #[test]
241    fn name_8() {
242        assert_eq!(
243            Reader::new("/$$".as_bytes())
244                .read_without_context::<Name<'_>>()
245                .unwrap()
246                .deref(),
247            b"$$"
248        );
249    }
250
251    #[test]
252    fn name_9() {
253        assert_eq!(
254            Reader::new("/@pattern".as_bytes())
255                .read_without_context::<Name<'_>>()
256                .unwrap()
257                .deref(),
258            b"@pattern"
259        );
260    }
261
262    #[test]
263    fn name_10() {
264        assert_eq!(
265            Reader::new("/.notdef".as_bytes())
266                .read_without_context::<Name<'_>>()
267                .unwrap()
268                .deref(),
269            b".notdef"
270        );
271    }
272
273    #[test]
274    fn name_11() {
275        assert_eq!(
276            Reader::new("/lime#20Green".as_bytes())
277                .read_without_context::<Name<'_>>()
278                .unwrap()
279                .deref(),
280            b"lime Green"
281        );
282    }
283
284    #[test]
285    fn name_12() {
286        assert_eq!(
287            Reader::new("/paired#28#29parentheses".as_bytes())
288                .read_without_context::<Name<'_>>()
289                .unwrap()
290                .deref(),
291            b"paired()parentheses"
292        );
293    }
294
295    #[test]
296    fn name_13() {
297        assert_eq!(
298            Reader::new("/The_Key_of_F#23_Minor".as_bytes())
299                .read_without_context::<Name<'_>>()
300                .unwrap()
301                .deref(),
302            b"The_Key_of_F#_Minor"
303        );
304    }
305
306    #[test]
307    fn name_14() {
308        assert_eq!(
309            Reader::new("/A#42".as_bytes())
310                .read_without_context::<Name<'_>>()
311                .unwrap()
312                .deref(),
313            b"AB"
314        );
315    }
316
317    #[test]
318    fn name_15() {
319        assert_eq!(
320            Reader::new("/A#3b".as_bytes())
321                .read_without_context::<Name<'_>>()
322                .unwrap()
323                .deref(),
324            b"A;"
325        );
326    }
327
328    #[test]
329    fn name_16() {
330        assert_eq!(
331            Reader::new("/A#3B".as_bytes())
332                .read_without_context::<Name<'_>>()
333                .unwrap()
334                .deref(),
335            b"A;"
336        );
337    }
338
339    #[test]
340    fn name_17() {
341        assert_eq!(
342            Reader::new("/k1  ".as_bytes())
343                .read_without_context::<Name<'_>>()
344                .unwrap()
345                .deref(),
346            b"k1"
347        );
348    }
349}