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