1fn is_whitespace(b: u8) -> bool {
23 matches!(b, 0x00 | 0x09 | 0x0A | 0x0C | 0x0D | 0x20 | 0x80 | 0xFF)
24}
25
26fn is_delimiter(b: u8) -> bool {
28 matches!(
29 b,
30 b'%' | b'(' | b')' | b'/' | b'<' | b'>' | b'[' | b']' | b'{' | b'}'
31 )
32}
33
34#[derive(Debug, Clone)]
42pub struct Words<'a> {
43 data: &'a [u8],
44 at: usize,
45}
46
47impl<'a> Words<'a> {
48 #[must_use]
50 pub fn new(bytes: &'a [u8]) -> Self {
51 Self { data: bytes, at: 0 }
52 }
53
54 fn skip_spaces_and_comments(&mut self) -> Option<u8> {
57 loop {
58 let mut c = *self.data.get(self.at)?;
59 self.at += 1;
60 while is_whitespace(c) {
61 c = *self.data.get(self.at)?;
62 self.at += 1;
63 }
64 if c != b'%' {
65 return Some(c);
66 }
67 loop {
68 let c = *self.data.get(self.at)?;
69 self.at += 1;
70 if c == b'\r' || c == b'\n' {
71 break;
72 }
73 }
74 }
75 }
76
77 fn since(&self, start: usize) -> &'a [u8] {
78 self.data.get(start..self.at).unwrap_or_default()
79 }
80
81 fn regular(&mut self, start: usize) -> &'a [u8] {
84 while let Some(&c) = self.data.get(self.at) {
85 if is_delimiter(c) || is_whitespace(c) {
86 break;
87 }
88 self.at += 1;
89 }
90 self.since(start)
91 }
92
93 fn name(&mut self, start: usize) -> &'a [u8] {
96 while let Some(&c) = self.data.get(self.at) {
97 if is_whitespace(c) || is_delimiter(c) {
98 return self.since(start);
99 }
100 self.at += 1;
101 }
102 &[]
103 }
104
105 fn angle_open(&mut self, start: usize) -> &'a [u8] {
108 let Some(&first) = self.data.get(self.at) else {
109 return self.since(start);
110 };
111 self.at += 1;
112 if first == b'<' {
113 return self.since(start);
114 }
115 let mut c = first;
116 while self.at < self.data.len() && c != b'>' {
117 c = self.data.get(self.at).copied().unwrap_or(b'>');
118 self.at += 1;
119 }
120 self.since(start)
121 }
122
123 fn angle_close(&mut self, start: usize) -> &'a [u8] {
125 if self.data.get(self.at) == Some(&b'>') {
126 self.at += 1;
127 }
128 self.since(start)
129 }
130
131 fn parens(&mut self, start: usize) -> &'a [u8] {
134 let mut level = 1i32;
135 while level > 0 {
136 let Some(&c) = self.data.get(self.at) else {
137 break;
138 };
139 self.at += 1;
140 match c {
141 b'(' => level += 1,
142 b')' => level -= 1,
143 _ => {}
144 }
145 }
146 self.since(start)
147 }
148}
149
150impl<'a> Iterator for Words<'a> {
151 type Item = &'a [u8];
152
153 fn next(&mut self) -> Option<Self::Item> {
154 let first = self.skip_spaces_and_comments()?;
155 let start = self.at.checked_sub(1)?;
156 let word = if is_delimiter(first) {
157 match first {
158 b'/' => self.name(start),
159 b'<' => self.angle_open(start),
160 b'>' => self.angle_close(start),
161 b'(' => self.parens(start),
162 _ => self.since(start),
164 }
165 } else {
166 self.regular(start)
167 };
168 (!word.is_empty()).then_some(word)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::Words;
175
176 fn words(bytes: &[u8]) -> Vec<&[u8]> {
177 Words::new(bytes).collect()
178 }
179
180 #[test]
181 fn splits_operators_and_hex_strings() {
182 assert_eq!(
183 words(b"1 begincidchar <20> <100> endcidchar"),
184 vec![&b"1"[..], b"begincidchar", b"<20>", b"<100>", b"endcidchar"]
185 );
186 }
187
188 #[test]
189 fn empty_input_yields_nothing() {
190 assert!(words(b"").is_empty());
191 assert!(words(b" \n\t").is_empty());
192 }
193
194 #[test]
195 fn comments_run_to_the_end_of_the_line() {
196 assert_eq!(words(b"a % comment here\nb"), vec![&b"a"[..], b"b"]);
197 assert_eq!(words(b"a % comment here"), vec![&b"a"[..]]);
199 assert_eq!(words(b"%one\n%two\nz"), vec![&b"z"[..]]);
201 }
202
203 #[test]
204 fn double_angle_is_its_own_word() {
205 assert_eq!(words(b"<</X 1>>"), vec![&b"<<"[..], b"/X", b"1", b">>"]);
206 assert_eq!(words(b"<<"), vec![&b"<<"[..]]);
207 assert_eq!(words(b">"), vec![&b">"[..]]);
208 assert_eq!(words(b">>"), vec![&b">>"[..]]);
209 }
210
211 #[test]
214 fn truncated_hex_strings_survive() {
215 assert_eq!(words(b"<"), vec![&b"<"[..]]);
216 assert_eq!(words(b"<a1"), vec![&b"<a1"[..]]);
217 assert_eq!(words(b"<a1>"), vec![&b"<a1>"[..]]);
218 }
219
220 #[test]
222 fn parenthesised_strings_are_returned_whole() {
223 assert_eq!(words(b"(a(b)c) x"), vec![&b"(a(b)c)"[..], b"x"]);
224 assert_eq!(words(b"(unterminated"), vec![&b"(unterminated"[..]]);
225 assert_eq!(words(br"(a\(b) x"), vec![&br"(a\(b) x"[..]]);
226 assert_eq!(words(b"(Japan1)"), vec![&b"(Japan1)"[..]]);
227 }
228
229 #[test]
232 fn a_name_at_end_of_data_ends_the_program() {
233 assert_eq!(words(b"a /Ordering"), vec![&b"a"[..]]);
234 assert_eq!(words(b"a /Ordering "), vec![&b"a"[..], b"/Ordering"]);
235 assert_eq!(words(b"a /Ordering("), vec![&b"a"[..], b"/Ordering", b"("]);
236 }
237
238 #[test]
239 fn bracket_delimiters_are_single_byte_words() {
240 assert_eq!(
241 words(b"[1]{2})"),
242 vec![&b"["[..], b"1", b"]", b"{", b"2", b"}", b")"]
243 );
244 }
245
246 #[test]
249 fn high_whitespace_bytes_separate_words() {
250 assert_eq!(words(&[b'a', 0x80, b'b']), vec![&b"a"[..], b"b"]);
251 assert_eq!(words(&[b'a', 0xFF, b'b']), vec![&b"a"[..], b"b"]);
252 assert_eq!(words(&[b'a', 0xFE, b'b']), vec![&[b'a', 0xFE, b'b'][..]]);
254 }
255
256 #[test]
259 fn tokens_stay_inside_the_input() {
260 let inputs: &[&[u8]] = &[
261 b"<<<<<<",
262 b"((((",
263 b"))))",
264 b"/",
265 b"%",
266 b"<>",
267 b"><",
268 &[0x00, 0xFF, 0x80, 0x25, 0x28],
269 &[0x3C; 64],
270 ];
271 for input in inputs {
272 let mut total = 0usize;
273 for w in Words::new(input) {
274 assert!(!w.is_empty());
275 assert!(w.len() <= input.len());
276 total += w.len();
277 assert!(total <= input.len(), "words overlap in {input:?}");
278 }
279 }
280 }
281 #[test]
285 fn a_cid_range_splits_into_five_words() {
286 let words: Vec<&[u8]> = Words::new(b"begincidrange <20> <7e> 1 endcidrange").collect();
287 assert_eq!(words.first().copied(), Some(&b"begincidrange"[..]));
288 assert_eq!(words.get(1).copied(), Some(&b"<20>"[..]));
290 assert_eq!(words.len(), 5);
291 }
292}