saphyr_parser/input/
str.rs1use crate::{
2 char_traits::{
3 is_alpha, is_blank, is_blank_or_breakz, is_break, is_breakz, is_digit, is_flow, is_z,
4 },
5 input::{Input, SkipTabs},
6};
7use alloc::string::String;
8
9#[allow(clippy::module_name_repetitions)]
11#[derive(Clone, Copy)]
12pub struct StrInput<'a> {
13 buffer: &'a str,
15 lookahead: usize,
20}
21
22impl<'a> StrInput<'a> {
23 #[must_use]
25 pub fn new(input: &'a str) -> Self {
26 Self {
27 buffer: input,
28 lookahead: 0,
29 }
30 }
31}
32
33impl Input for StrInput<'_> {
34 #[inline]
35 fn lookahead(&mut self, x: usize) {
36 self.lookahead = self.lookahead.max(x);
40 }
41
42 #[inline]
43 fn buflen(&self) -> usize {
44 self.lookahead
45 }
46
47 #[inline]
48 fn bufmaxlen(&self) -> usize {
49 BUFFER_LEN
50 }
51
52 fn buf_is_empty(&self) -> bool {
53 self.buflen() == 0
54 }
55
56 #[inline]
57 fn raw_read_ch(&mut self) -> char {
58 let mut chars = self.buffer.chars();
59 if let Some(c) = chars.next() {
60 self.buffer = chars.as_str();
61 c
62 } else {
63 '\0'
64 }
65 }
66
67 #[inline]
68 fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
69 if let Some((c, sub_str)) = split_first_char(self.buffer) {
70 if is_breakz(c) {
71 None
72 } else {
73 self.buffer = sub_str;
74 Some(c)
75 }
76 } else {
77 None
78 }
79 }
80
81 #[inline]
82 fn skip(&mut self) {
83 let mut chars = self.buffer.chars();
84 if chars.next().is_some() {
85 self.buffer = chars.as_str();
86 }
87 }
88
89 #[inline]
90 fn skip_n(&mut self, count: usize) {
91 let mut chars = self.buffer.chars();
92 for _ in 0..count {
93 if chars.next().is_none() {
94 break;
95 }
96 }
97 self.buffer = chars.as_str();
98 }
99
100 #[inline]
101 fn peek(&self) -> char {
102 self.buffer.chars().next().unwrap_or('\0')
103 }
104
105 #[inline]
106 fn peek_nth(&self, n: usize) -> char {
107 let mut chars = self.buffer.chars();
108 for _ in 0..n {
109 if chars.next().is_none() {
110 return '\0';
111 }
112 }
113 chars.next().unwrap_or('\0')
114 }
115
116 #[inline]
117 fn peek_opt(&self) -> Option<char> {
118 self.buffer.chars().next()
119 }
120
121 #[inline]
122 fn look_ch(&mut self) -> char {
123 self.lookahead(1);
124 self.peek()
125 }
126
127 #[inline]
128 fn next_char_is(&self, c: char) -> bool {
129 self.peek() == c
130 }
131
132 #[inline]
133 fn nth_char_is(&self, n: usize, c: char) -> bool {
134 self.peek_nth(n) == c
135 }
136
137 #[inline]
138 fn next_2_are(&self, c1: char, c2: char) -> bool {
139 let mut chars = self.buffer.chars();
140 chars.next() == Some(c1) && chars.next() == Some(c2)
141 }
142
143 #[inline]
144 fn next_3_are(&self, c1: char, c2: char, c3: char) -> bool {
145 let mut chars = self.buffer.chars();
146 chars.next() == Some(c1) && chars.next() == Some(c2) && chars.next() == Some(c3)
147 }
148
149 #[inline]
150 fn next_is_document_indicator(&self) -> bool {
151 if self.buffer.len() < 3 {
152 false
153 } else {
154 let bytes = self.buffer.as_bytes();
156 (bytes.len() == 3 || is_blank_or_breakz(bytes[3] as char))
157 && (bytes[0] == b'.' || bytes[0] == b'-')
158 && bytes[0] == bytes[1]
159 && bytes[1] == bytes[2]
160 }
161 }
162
163 #[inline]
164 fn next_is_document_start(&self) -> bool {
165 if self.buffer.len() < 3 {
166 false
167 } else {
168 let bytes = self.buffer.as_bytes();
170 (bytes.len() == 3 || is_blank_or_breakz(bytes[3] as char))
171 && bytes[0] == b'-'
172 && bytes[1] == b'-'
173 && bytes[2] == b'-'
174 }
175 }
176
177 #[inline]
178 fn next_is_document_end(&self) -> bool {
179 if self.buffer.len() < 3 {
180 false
181 } else {
182 let bytes = self.buffer.as_bytes();
184 (bytes.len() == 3 || is_blank_or_breakz(bytes[3] as char))
185 && bytes[0] == b'.'
186 && bytes[1] == b'.'
187 && bytes[2] == b'.'
188 }
189 }
190
191 fn skip_ws_to_eol(&mut self, skip_tabs: SkipTabs) -> (usize, Result<SkipTabs, &'static str>) {
192 assert!(!matches!(skip_tabs, SkipTabs::Result(..)));
193
194 let mut new_str = self.buffer;
195 let mut has_yaml_ws = false;
196 let mut encountered_tab = false;
197
198 if skip_tabs == SkipTabs::Yes {
201 loop {
202 if let Some(sub_str) = new_str.strip_prefix(' ') {
203 has_yaml_ws = true;
204 new_str = sub_str;
205 } else if let Some(sub_str) = new_str.strip_prefix('\t') {
206 encountered_tab = true;
207 new_str = sub_str;
208 } else {
209 break;
210 }
211 }
212 } else {
213 while let Some(sub_str) = new_str.strip_prefix(' ') {
214 has_yaml_ws = true;
215 new_str = sub_str;
216 }
217 }
218
219 let mut chars_consumed = self.buffer.len() - new_str.len();
222
223 if !new_str.is_empty() && new_str.as_bytes()[0] == b'#' {
224 if !encountered_tab && !has_yaml_ws {
225 return (
226 chars_consumed,
227 Err("comments must be separated from other tokens by whitespace"),
228 );
229 }
230
231 while let Some((c, sub_str)) = split_first_char(new_str) {
233 if is_breakz(c) {
234 break;
235 }
236 new_str = sub_str;
237 chars_consumed += 1;
238 }
239 }
240
241 self.buffer = new_str;
242
243 (
244 chars_consumed,
245 Ok(SkipTabs::Result(encountered_tab, has_yaml_ws)),
246 )
247 }
248
249 #[allow(clippy::inline_always)]
250 #[inline(always)]
251 fn next_can_be_plain_scalar(&self, in_flow: bool) -> bool {
252 let nc = self.peek_nth(1);
253 match self.peek() {
254 ':' if is_blank_or_breakz(nc) || (in_flow && is_flow(nc)) => false,
256 c if in_flow && is_flow(c) => false,
257 _ => true,
258 }
259 }
260
261 #[inline]
262 fn next_is_blank_or_break(&self) -> bool {
263 !self.buffer.is_empty()
264 && (is_blank(self.buffer.as_bytes()[0] as char)
265 || is_break(self.buffer.as_bytes()[0] as char))
266 }
267
268 #[inline]
269 fn next_is_blank_or_breakz(&self) -> bool {
270 self.buffer.is_empty()
271 || (is_blank(self.buffer.as_bytes()[0] as char)
272 || is_breakz(self.buffer.as_bytes()[0] as char))
273 }
274
275 #[inline]
276 fn next_is_blank(&self) -> bool {
277 !self.buffer.is_empty() && is_blank(self.buffer.as_bytes()[0] as char)
278 }
279
280 #[inline]
281 fn next_is_break(&self) -> bool {
282 !self.buffer.is_empty() && is_break(self.buffer.as_bytes()[0] as char)
283 }
284
285 #[inline]
286 fn next_is_breakz(&self) -> bool {
287 self.buffer.is_empty() || is_breakz(self.buffer.as_bytes()[0] as char)
288 }
289
290 #[inline]
291 fn next_is_z(&self) -> bool {
292 self.buffer.is_empty() || is_z(self.buffer.as_bytes()[0] as char)
293 }
294
295 #[inline]
296 fn next_is_flow(&self) -> bool {
297 !self.buffer.is_empty() && is_flow(self.buffer.as_bytes()[0] as char)
298 }
299
300 #[inline]
301 fn next_is_digit(&self) -> bool {
302 !self.buffer.is_empty() && is_digit(self.buffer.as_bytes()[0] as char)
303 }
304
305 #[inline]
306 fn next_is_alpha(&self) -> bool {
307 !self.buffer.is_empty() && is_alpha(self.buffer.as_bytes()[0] as char)
308 }
309
310 fn skip_while_non_breakz(&mut self) -> usize {
311 let mut new_str = self.buffer;
312 let mut count = 0;
313
314 while let Some((c, sub_str)) = split_first_char(new_str) {
316 if is_breakz(c) {
317 break;
318 }
319 new_str = sub_str;
320 count += 1;
321 }
322
323 self.buffer = new_str;
324
325 count
326 }
327
328 fn skip_while_blank(&mut self) -> usize {
329 let mut i = 0;
331 while i < self.buffer.len() {
332 if !is_blank(self.buffer.as_bytes()[i] as char) {
333 break;
334 }
335 i += 1;
336 }
337 self.buffer = &self.buffer[i..];
338 i
339 }
340
341 fn fetch_while_is_alpha(&mut self, out: &mut String) -> usize {
342 let mut not_alpha = None;
343
344 let mut chars = self.buffer.chars();
346 for c in chars.by_ref() {
347 if !is_alpha(c) {
348 not_alpha = Some(c);
349 break;
350 }
351 }
352
353 let remaining_string = if let Some(c) = not_alpha {
354 let n_bytes_read = chars.as_str().as_ptr() as usize - self.buffer.as_ptr() as usize;
355 let last_char_bytes = c.len_utf8();
356 &self.buffer[n_bytes_read - last_char_bytes..]
357 } else {
358 chars.as_str()
359 };
360
361 let n_bytes_to_append = remaining_string.as_ptr() as usize - self.buffer.as_ptr() as usize;
362 out.reserve(n_bytes_to_append);
363 out.push_str(&self.buffer[..n_bytes_to_append]);
364 self.buffer = remaining_string;
365
366 n_bytes_to_append
367 }
368
369 fn fetch_while_is_yaml_non_space(&mut self, out: &mut String) -> usize {
370 let mut not_non_space = None;
371
372 let mut chars = self.buffer.chars();
374 for c in chars.by_ref() {
375 if !crate::char_traits::is_yaml_non_space(c) {
376 not_non_space = Some(c);
377 break;
378 }
379 }
380
381 let remaining_string = if let Some(c) = not_non_space {
382 let n_bytes_read = chars.as_str().as_ptr() as usize - self.buffer.as_ptr() as usize;
383 let last_char_bytes = c.len_utf8();
384 &self.buffer[n_bytes_read - last_char_bytes..]
385 } else {
386 chars.as_str()
387 };
388
389 let n_bytes_to_append = remaining_string.as_ptr() as usize - self.buffer.as_ptr() as usize;
390 out.reserve(n_bytes_to_append);
391 out.push_str(&self.buffer[..n_bytes_to_append]);
392 self.buffer = remaining_string;
393
394 n_bytes_to_append
395 }
396}
397
398const BUFFER_LEN: usize = 128;
421
422#[inline]
425fn split_first_char(s: &str) -> Option<(char, &str)> {
426 let mut chars = s.chars();
427 let c = chars.next()?;
428 Some((c, chars.as_str()))
429}
430
431#[cfg(test)]
432mod test {
433 use crate::input::Input;
434
435 use super::StrInput;
436
437 #[test]
438 pub fn is_document_start() {
439 let input = StrInput::new("---\n");
440 assert!(input.next_is_document_start());
441 assert!(input.next_is_document_indicator());
442 let input = StrInput::new("---");
443 assert!(input.next_is_document_start());
444 assert!(input.next_is_document_indicator());
445 let input = StrInput::new("...\n");
446 assert!(!input.next_is_document_start());
447 assert!(input.next_is_document_indicator());
448 let input = StrInput::new("--- ");
449 assert!(input.next_is_document_start());
450 assert!(input.next_is_document_indicator());
451 }
452
453 #[test]
454 pub fn is_document_end() {
455 let input = StrInput::new("...\n");
456 assert!(input.next_is_document_end());
457 assert!(input.next_is_document_indicator());
458 let input = StrInput::new("...");
459 assert!(input.next_is_document_end());
460 assert!(input.next_is_document_indicator());
461 let input = StrInput::new("---\n");
462 assert!(!input.next_is_document_end());
463 assert!(input.next_is_document_indicator());
464 let input = StrInput::new("... ");
465 assert!(input.next_is_document_end());
466 assert!(input.next_is_document_indicator());
467 }
468}