binread/
punctuated.rs

1//! A module for [`Punctuated<T, P>`](Punctuated), a series of items to parse of type T separated
2//! by punction of type `P`.
3
4use crate::io::{Read, Seek};
5use crate::{BinRead, BinResult, ReadOptions};
6#[cfg(not(feature = "std"))]
7use alloc::vec::Vec;
8use core::fmt;
9
10/// A type for seperated data. Since parsing for this type is ambiguous, you must manually specify
11/// a parser using the `parse_with` attribute.
12///
13/// ## Example
14///
15/// ```rust
16/// # use binread::{*, io::*};
17/// use binread::punctuated::Punctuated;
18///
19/// #[derive(BinRead)]
20/// struct MyList {
21///     #[br(parse_with = Punctuated::separated)]
22///     #[br(count = 3)]
23///     x: Punctuated<u16, u8>,
24/// }
25///
26/// # let mut x = Cursor::new(b"\0\x03\0\0\x02\x01\0\x01");
27/// # let y: MyList = x.read_be().unwrap();
28/// # assert_eq!(*y.x, vec![3, 2, 1]);
29/// # assert_eq!(y.x.seperators, vec![0, 1]);
30/// ```
31pub struct Punctuated<T: BinRead, P: BinRead> {
32    data: Vec<T>,
33    pub seperators: Vec<P>,
34}
35
36impl<C: Copy + 'static, T: BinRead<Args = C>, P: BinRead<Args = ()>> Punctuated<T, P> {
37    /// A parser for values seperated by another value, with no trailing punctuation.
38    ///
39    /// Requires a specified count.
40    ///
41    /// ## Example
42    ///
43    /// ```rust
44    /// # use binread::{*, io::*};
45    /// use binread::punctuated::Punctuated;
46    ///
47    /// #[derive(BinRead)]
48    /// struct MyList {
49    ///     #[br(parse_with = Punctuated::separated)]
50    ///     #[br(count = 3)]
51    ///     x: Punctuated<u16, u8>,
52    /// }
53    ///
54    /// # let mut x = Cursor::new(b"\0\x03\0\0\x02\x01\0\x01");
55    /// # let y: MyList = x.read_be().unwrap();
56    /// # assert_eq!(*y.x, vec![3, 2, 1]);
57    /// # assert_eq!(y.x.seperators, vec![0, 1]);
58    /// ```
59    pub fn separated<R: Read + Seek>(
60        reader: &mut R,
61        options: &ReadOptions,
62        args: C,
63    ) -> BinResult<Self> {
64        let count = match options.count {
65            Some(x) => x,
66            None => panic!("Missing count for Punctuated"),
67        };
68
69        let mut data = Vec::with_capacity(count);
70        let mut seperators = Vec::with_capacity(count.max(1) - 1);
71
72        for i in 0..count {
73            data.push(T::read_options(reader, &options, args)?);
74            if i + 1 != count {
75                seperators.push(P::read_options(reader, options, ())?);
76            }
77        }
78
79        Ok(Self { data, seperators })
80    }
81
82    /// A parser for values seperated by another value, with trailing punctuation.
83    ///
84    /// Requires a specified count.
85    pub fn separated_trailing<R: Read + Seek>(
86        reader: &mut R,
87        options: &ReadOptions,
88        args: C,
89    ) -> BinResult<Self> {
90        let count = match options.count {
91            Some(x) => x,
92            None => panic!("Missing count for Punctuated"),
93        };
94
95        let mut data = Vec::with_capacity(count);
96        let mut seperators = Vec::with_capacity(count);
97
98        for _ in 0..count {
99            data.push(T::read_options(reader, &options, args)?);
100            seperators.push(P::read_options(reader, options, ())?);
101        }
102
103        Ok(Self { data, seperators })
104    }
105}
106
107impl<T: BinRead + fmt::Debug, P: BinRead> fmt::Debug for Punctuated<T, P> {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        self.data.fmt(f)
110    }
111}
112
113impl<T: BinRead, P: BinRead> core::ops::Deref for Punctuated<T, P> {
114    type Target = Vec<T>;
115
116    fn deref(&self) -> &Self::Target {
117        &self.data
118    }
119}
120
121impl<T: BinRead, P: BinRead> core::ops::DerefMut for Punctuated<T, P> {
122    fn deref_mut(&mut self) -> &mut Self::Target {
123        &mut self.data
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate as binread;
131
132    use binread::{io::Cursor, BinRead, BinReaderExt};
133
134    #[derive(BinRead, Clone, Copy, Debug)]
135    #[br(magic = 1u8)]
136    struct One;
137
138    #[derive(BinRead, Clone, Copy, Debug)]
139    #[br(magic = 2u8)]
140    struct Two;
141
142    #[derive(BinRead)]
143    struct PunctuatedTest {
144        count: u8,
145
146        #[br(count = count)]
147        #[br(parse_with = Punctuated::separated)]
148        list: Punctuated<One, Two>,
149    }
150
151    #[derive(BinRead)]
152    struct PunctuatedTestTrailing {
153        count: u8,
154
155        #[br(count = count)]
156        #[br(parse_with = Punctuated::separated_trailing)]
157        list: Punctuated<One, Two>,
158    }
159
160    #[derive(BinRead)]
161    struct MissingCount {
162        #[br(parse_with = Punctuated::separated)]
163        _list: Punctuated<One, Two>,
164    }
165
166    #[derive(BinRead)]
167    struct MissingCountTrailing {
168        #[br(parse_with = Punctuated::separated_trailing)]
169        _list: Punctuated<One, Two>,
170    }
171
172    const TEST_DATA: &[u8] = b"\x03\x01\x02\x01\x02\x01";
173    const TEST_DATA_TRAILING: &[u8] = b"\x03\x01\x02\x01\x02\x01\x02";
174
175    #[test]
176    fn punctuated() {
177        let mut x = Cursor::new(TEST_DATA);
178
179        let y: PunctuatedTest = x.read_be().unwrap();
180
181        assert_eq!(y.count, 3);
182        assert_eq!(y.list.len(), 3);
183
184        // This behavior may be reworked later
185        assert_eq!(format!("{:?}", y.list), "[One, One, One]");
186    }
187
188    #[test]
189    fn punctuated_trailing() {
190        let mut x = Cursor::new(TEST_DATA_TRAILING);
191
192        let mut y: PunctuatedTestTrailing = x.read_be().unwrap();
193
194        assert_eq!(y.count, 3);
195        assert_eq!(y.list.len(), 3);
196
197        let y = &mut *y.list;
198        y[0] = y[1];
199    }
200
201    #[test]
202    #[should_panic]
203    fn missing_count() {
204        let mut x = Cursor::new(TEST_DATA);
205
206        let _: MissingCount = x.read_be().unwrap();
207    }
208
209    #[test]
210    #[should_panic]
211    fn missing_count_trailing() {
212        let mut x = Cursor::new(TEST_DATA);
213
214        let _: MissingCountTrailing = x.read_be().unwrap();
215    }
216}