granit_parser/input.rs
1//! Utilities to create a source of input to the parser.
2//!
3//! [`Input`] must be implemented for the parser to fetch input. Make sure your needs aren't
4//! covered by the [`BufferedInput`].
5
6use alloc::string::String;
7
8use crate::error::ErrorKind;
9
10pub(crate) mod buffered;
11pub(crate) mod str;
12
13#[allow(clippy::module_name_repetitions)]
14pub use buffered::{BufferedInput, FallibleBufferedInput};
15
16/// A trait for inputs that can provide borrowed slices with a specific lifetime.
17///
18/// This trait enables zero-copy (`Cow::Borrowed`) token values for inputs that keep a stable
19/// backing string. The key difference from [`Input::slice_bytes`] is that this method returns
20/// a slice with the input's original lifetime `'a`, not tied to `&self`.
21///
22/// For inputs that support zero-copy (like [`str::StrInput`]), this returns `Some(&'a str)`.
23/// For streaming inputs that don't have stable backing storage, this returns `None`.
24pub trait BorrowedInput<'a>: Input {
25 /// Return a borrowed slice of the underlying source between two byte offsets.
26 ///
27 /// Unlike [`Input::slice_bytes`], this returns a slice with the input's lifetime `'a`,
28 /// allowing the slice to outlive the borrow of `&self`.
29 ///
30 /// `start` and `end` are byte offsets as returned by [`Input::byte_offset`]. The interval is
31 /// half-open: `[start, end)`.
32 ///
33 /// Returns `None` if the input does not support zero-copy slicing.
34 #[must_use]
35 fn slice_borrowed(&self, start: usize, end: usize) -> Option<&'a str>;
36}
37
38pub use crate::char_traits::{
39 is_alpha, is_blank, is_blank_or_breakz, is_break, is_breakz, is_digit, is_flow, is_z,
40};
41
42/// Interface for a source of characters.
43///
44/// Hiding the input's implementation behind this trait allows input-specific optimizations, such
45/// as using `str` methods instead of manually transferring one `char` at a time to a buffer.
46/// Implementations with stable backing storage can also return borrowed `&str` slices and avoid
47/// allocating token values.
48pub trait Input {
49 /// A hint to the input source that we will need to read `count` characters.
50 ///
51 /// If the input is exhausted, `\0` can be used to pad the last characters and later returned.
52 /// The characters must not be consumed, but may be placed in an internal buffer.
53 ///
54 /// This method may be a no-op if buffering yields no performance improvement.
55 ///
56 /// Implementers of [`Input`] must _not_ expose a lookahead window larger than
57 /// [`Input::bufmaxlen`]. They may retain a larger window requested by an earlier call; callers
58 /// should use [`Input::buflen`] to observe the currently available window.
59 fn lookahead(&mut self, count: usize);
60
61 /// Return the number of characters in the active lookahead window.
62 ///
63 /// This is the number of characters that the input promises can be read through [`peek`] and
64 /// [`peek_nth`] after prior [`lookahead`] calls. It is not necessarily the number of source
65 /// characters remaining: inputs may keep the window available after consuming characters and
66 /// may pad positions past EOF with `\0`.
67 ///
68 /// [`lookahead`]: Input::lookahead
69 /// [`peek`]: Input::peek
70 /// [`peek_nth`]: Input::peek_nth
71 #[must_use]
72 fn buflen(&self) -> usize;
73
74 /// Return the maximum number of characters this input can buffer for lookahead.
75 #[must_use]
76 fn bufmaxlen(&self) -> usize;
77
78 /// Return whether the active lookahead window is empty.
79 ///
80 /// This is equivalent to `self.buflen() == 0`. It does not mean the underlying source is
81 /// exhausted: after a previous [`lookahead`] call, an input may keep a non-empty lookahead
82 /// window available even after all source characters have been consumed, with positions past
83 /// EOF observed as `\0`.
84 ///
85 /// [`lookahead`]: Input::lookahead
86 #[inline]
87 #[must_use]
88 fn buf_is_empty(&self) -> bool {
89 self.buflen() == 0
90 }
91
92 /// Read the next character from the logical input stream and return it directly.
93 ///
94 /// If an implementation has already fetched characters for lookahead, this consumes the
95 /// buffered stream front before reading farther from the underlying source.
96 #[must_use]
97 fn raw_read_ch(&mut self) -> char;
98
99 /// Read a non-breakz character from the input stream and return it directly.
100 ///
101 /// If an implementation has already fetched characters for lookahead, this consumes from the
102 /// buffered stream front before reading farther from the underlying source.
103 ///
104 /// If the next character is a breakz, it is either not consumed or placed into the buffer (if
105 /// any).
106 #[must_use]
107 fn raw_read_non_breakz_ch(&mut self) -> Option<char>;
108
109 /// Consume the next character.
110 fn skip(&mut self);
111
112 /// Consume the next `count` characters.
113 fn skip_n(&mut self, count: usize);
114
115 /// Return the next character, without consuming it.
116 ///
117 /// Users of the [`Input`] must make sure that the character has been loaded through a prior
118 /// call to [`Input::lookahead`]. Implementors of [`Input`] may assume that a valid call to
119 /// [`Input::lookahead`] has been made beforehand.
120 ///
121 /// # Return
122 /// If the input source is not exhausted, returns the next character to be fed into the
123 /// scanner. Otherwise, returns `\0`.
124 #[must_use]
125 fn peek(&self) -> char;
126
127 /// Return the `n`-th character in the buffer, without consuming it.
128 ///
129 /// This function assumes that the `n`-th character in the input has already been fetched through
130 /// [`Input::lookahead`].
131 #[must_use]
132 fn peek_nth(&self, n: usize) -> char;
133
134 /// Return the current byte offset in the underlying source, if available.
135 ///
136 /// This is an *optional* capability that enables zero-copy (`Cow::Borrowed`) token values
137 /// for inputs that keep a stable backing string (notably [`str::StrInput`]).
138 ///
139 /// The returned value (when `Some`) is the number of bytes that have been consumed so far,
140 /// i.e. an offset into the original source string.
141 ///
142 /// # Correctness contract
143 /// Implementations returning `Some(_)` must satisfy all of the following:
144 ///
145 /// - The offset is a valid UTF-8 boundary in the underlying source.
146 /// - The offset is monotonically non-decreasing as characters are consumed.
147 /// - The underlying source is stable for the duration of parsing (no reallocation/mutation)
148 /// so that slices returned by [`Input::slice_bytes`] remain valid.
149 ///
150 /// Inputs that cannot provide stable slicing (e.g. stream/iterator inputs) must return
151 /// `None`.
152 #[inline]
153 #[must_use]
154 fn byte_offset(&self) -> Option<usize> {
155 None
156 }
157
158 /// Return a borrowed slice of the underlying source between two byte offsets.
159 ///
160 /// This is an *optional* capability used to produce `Cow::Borrowed` values without
161 /// allocating.
162 ///
163 /// `start` and `end` are byte offsets as returned by [`Input::byte_offset`]. The interval is
164 /// half-open: `[start, end)`.
165 ///
166 /// # Correctness contract
167 /// Implementations returning `Some(&str)` must ensure:
168 ///
169 /// - `start <= end`.
170 /// - Both offsets are valid UTF-8 boundaries.
171 /// - The returned `&str` is a view into the stable underlying source associated with this
172 /// input.
173 ///
174 /// Implementations that return `None` from [`Input::byte_offset`] must also return `None`
175 /// here.
176 #[inline]
177 #[must_use]
178 fn slice_bytes(&self, _start: usize, _end: usize) -> Option<&str> {
179 None
180 }
181
182 /// Return whether this input may contain a `#` character.
183 ///
184 /// This is a conservative performance hint. Inputs that cannot answer cheaply should return
185 /// `true`, which keeps full comment handling enabled.
186 #[inline]
187 #[must_use]
188 fn may_contain_comments(&self) -> bool {
189 true
190 }
191
192 /// Take a terminal error reported by the underlying source.
193 ///
194 /// Infallible inputs use the default implementation. Fallible streaming inputs latch their
195 /// first source error and return it here so the scanner can distinguish the failure from clean
196 /// end-of-input. Once an implementation reports an error, it must not read from its source
197 /// again.
198 ///
199 /// Source adapters should use an input-related [`ErrorKind`] such as
200 /// [`ErrorKind::InputIo`], [`ErrorKind::InputDecoding`], or
201 /// [`ErrorKind::InputByteLimitExceeded`].
202 #[inline]
203 fn take_source_error(&mut self) -> Option<ErrorKind> {
204 None
205 }
206
207 /// Look for the next character and return it.
208 ///
209 /// The character is not consumed.
210 /// Equivalent to calling [`Input::lookahead`] and [`Input::peek`].
211 #[inline]
212 #[must_use]
213 fn look_ch(&mut self) -> char {
214 self.lookahead(1);
215 self.peek()
216 }
217
218 /// Return whether the next character in the input source is equal to `c`.
219 ///
220 /// This function assumes that the next character in the input has already been fetched through
221 /// [`Input::lookahead`].
222 #[inline]
223 #[must_use]
224 fn next_char_is(&self, c: char) -> bool {
225 self.peek() == c
226 }
227
228 /// Return whether the `n`-th character in the input source is equal to `c`.
229 ///
230 /// This function assumes that the `n`-th character in the input has already been fetched through
231 /// [`Input::lookahead`].
232 #[inline]
233 #[must_use]
234 fn nth_char_is(&self, n: usize, c: char) -> bool {
235 self.peek_nth(n) == c
236 }
237
238 /// Return whether the next 2 characters in the input source match the given characters.
239 ///
240 /// This function assumes that the next 2 characters in the input have already been fetched
241 /// through [`Input::lookahead`].
242 #[inline]
243 #[must_use]
244 fn next_2_are(&self, c1: char, c2: char) -> bool {
245 assert!(self.buflen() >= 2);
246 self.peek() == c1 && self.peek_nth(1) == c2
247 }
248
249 /// Return whether the next 3 characters in the input source match the given characters.
250 ///
251 /// This function assumes that the next 3 characters in the input have already been fetched
252 /// through [`Input::lookahead`].
253 #[inline]
254 #[must_use]
255 fn next_3_are(&self, c1: char, c2: char, c3: char) -> bool {
256 assert!(self.buflen() >= 3);
257 self.peek() == c1 && self.peek_nth(1) == c2 && self.peek_nth(2) == c3
258 }
259
260 /// Check whether the next characters correspond to a document indicator.
261 ///
262 /// This function assumes that the next 4 characters in the input have already been fetched
263 /// through [`Input::lookahead`].
264 #[inline]
265 #[must_use]
266 fn next_is_document_indicator(&self) -> bool {
267 assert!(self.buflen() >= 4);
268 is_blank_or_breakz(self.peek_nth(3))
269 && (self.next_3_are('.', '.', '.') || self.next_3_are('-', '-', '-'))
270 }
271
272 /// Check whether the next characters correspond to a start of document.
273 ///
274 /// This function assumes that the next 4 characters in the input have already been fetched
275 /// through [`Input::lookahead`].
276 #[inline]
277 #[must_use]
278 fn next_is_document_start(&self) -> bool {
279 assert!(self.buflen() >= 4);
280 self.next_3_are('-', '-', '-') && is_blank_or_breakz(self.peek_nth(3))
281 }
282
283 /// Check whether the next characters correspond to an end of document.
284 ///
285 /// This function assumes that the next 4 characters in the input have already been fetched
286 /// through [`Input::lookahead`].
287 #[inline]
288 #[must_use]
289 fn next_is_document_end(&self) -> bool {
290 assert!(self.buflen() >= 4);
291 self.next_3_are('.', '.', '.') && is_blank_or_breakz(self.peek_nth(3))
292 }
293
294 /// Skip YAML whitespace up to the end of the current line.
295 ///
296 /// Inline comments are consumed only after at least one preceding YAML whitespace character.
297 ///
298 /// # Return
299 /// Return a tuple with the number of characters that were consumed and the result of skipping
300 /// whitespace. The number of characters returned can be used to advance the index and column,
301 /// since no end-of-line character will be consumed.
302 /// See [`SkipTabs`] for more details on the success variant.
303 ///
304 /// # Errors
305 /// Returns [`ErrorKind::CommentNotSeparated`] if a comment is encountered without preceding
306 /// whitespace. In that event, the first tuple element contains the number of characters
307 /// consumed prior to reaching the `#`.
308 fn skip_ws_to_eol(&mut self, skip_tabs: SkipTabs) -> (usize, Result<SkipTabs, ErrorKind>) {
309 let mut encountered_tab = false;
310 let mut has_yaml_ws = false;
311 let mut chars_consumed = 0;
312 loop {
313 match self.look_ch() {
314 ' ' => {
315 has_yaml_ws = true;
316 self.skip();
317 }
318 '\t' if skip_tabs != SkipTabs::No => {
319 encountered_tab = true;
320 self.skip();
321 }
322 // YAML comments must be preceded by whitespace.
323 '#' if !encountered_tab && !has_yaml_ws => {
324 return (chars_consumed, Err(ErrorKind::CommentNotSeparated));
325 }
326 '#' => {
327 self.skip(); // Skip over '#'
328 while !is_breakz(self.look_ch()) {
329 self.skip();
330 chars_consumed += 1;
331 }
332 }
333 _ => break,
334 }
335 chars_consumed += 1;
336 }
337
338 (
339 chars_consumed,
340 Ok(SkipTabs::Result(encountered_tab, has_yaml_ws)),
341 )
342 }
343
344 /// Skip YAML blank characters, stopping before comments, line breaks, or other content.
345 ///
346 /// This is the comment-aware counterpart to [`Input::skip_ws_to_eol`]: it preserves a
347 /// following `#` for the scanner to tokenize while still letting input implementations batch
348 /// the common run of spaces and tabs.
349 ///
350 /// # Return
351 /// Returns the number of consumed characters and a [`SkipTabs::Result`] describing whether
352 /// tabs and valid YAML whitespace (` `) were encountered.
353 fn skip_ws_to_eol_blanks(&mut self, skip_tabs: SkipTabs) -> (usize, SkipTabs) {
354 assert!(!matches!(skip_tabs, SkipTabs::Result(..)));
355
356 let mut encountered_tab = false;
357 let mut has_yaml_ws = false;
358 let mut chars_consumed = 0;
359
360 loop {
361 match self.look_ch() {
362 ' ' => {
363 has_yaml_ws = true;
364 chars_consumed += 1;
365 self.skip();
366 }
367 '\t' if skip_tabs != SkipTabs::No => {
368 encountered_tab = true;
369 chars_consumed += 1;
370 self.skip();
371 }
372 _ => break,
373 }
374 }
375
376 (
377 chars_consumed,
378 SkipTabs::Result(encountered_tab, has_yaml_ws),
379 )
380 }
381
382 /// Check whether the next characters may be part of a plain scalar.
383 ///
384 /// This function assumes we are not given a blankz character.
385 #[allow(clippy::inline_always)]
386 #[inline(always)]
387 fn next_can_be_plain_scalar(&self, in_flow: bool) -> bool {
388 let nc = self.peek_nth(1);
389 match self.peek() {
390 // indicators can end a plain scalar, see 7.3.3. Plain Style
391 ':' if is_blank_or_breakz(nc) || (in_flow && is_flow(nc)) => false,
392 c if in_flow && is_flow(c) => false,
393 _ => true,
394 }
395 }
396
397 /// Check whether the next character is [a blank] or [a break].
398 ///
399 /// The character must have previously been fetched through [`lookahead`]
400 ///
401 /// # Return
402 /// Returns true if the character is [a blank] or [a break], false otherwise.
403 ///
404 /// [`lookahead`]: Input::lookahead
405 /// [a blank]: is_blank
406 /// [a break]: is_break
407 #[inline]
408 fn next_is_blank_or_break(&self) -> bool {
409 is_blank(self.peek()) || is_break(self.peek())
410 }
411
412 /// Check whether the next character is [a blank] or [a breakz].
413 ///
414 /// The character must have previously been fetched through [`lookahead`]
415 ///
416 /// # Return
417 /// Returns true if the character is [a blank] or [a break], false otherwise.
418 ///
419 /// [`lookahead`]: Input::lookahead
420 /// [a blank]: is_blank
421 /// [a breakz]: is_breakz
422 #[inline]
423 fn next_is_blank_or_breakz(&self) -> bool {
424 is_blank(self.peek()) || is_breakz(self.peek())
425 }
426
427 /// Check whether the next character is [a blank].
428 ///
429 /// The character must have previously been fetched through [`lookahead`]
430 ///
431 /// # Return
432 /// Returns true if the character is [a blank], false otherwise.
433 ///
434 /// [`lookahead`]: Input::lookahead
435 /// [a blank]: is_blank
436 #[inline]
437 fn next_is_blank(&self) -> bool {
438 is_blank(self.peek())
439 }
440
441 /// Check whether the next character is [a break].
442 ///
443 /// The character must have previously been fetched through [`lookahead`]
444 ///
445 /// # Return
446 /// Returns true if the character is [a break], false otherwise.
447 ///
448 /// [`lookahead`]: Input::lookahead
449 /// [a break]: is_break
450 #[inline]
451 fn next_is_break(&self) -> bool {
452 is_break(self.peek())
453 }
454
455 /// Check whether the next character is [a breakz].
456 ///
457 /// The character must have previously been fetched through [`lookahead`]
458 ///
459 /// # Return
460 /// Returns true if the character is [a breakz], false otherwise.
461 ///
462 /// [`lookahead`]: Input::lookahead
463 /// [a breakz]: is_breakz
464 #[inline]
465 fn next_is_breakz(&self) -> bool {
466 is_breakz(self.peek())
467 }
468
469 /// Check whether the input is at its physical end.
470 ///
471 /// The default implementation infers end-of-input from the `\0` sentinel returned by
472 /// [`Self::peek`]. Inputs that can distinguish a literal NUL from end-of-input should override
473 /// this method.
474 /// The next position must have previously been fetched through [`Self::lookahead`].
475 ///
476 /// # Return
477 /// Returns true if the input is exhausted, false otherwise.
478 #[inline]
479 fn next_is_z(&self) -> bool {
480 is_z(self.peek())
481 }
482
483 /// Check whether the next character is [a flow].
484 ///
485 /// The character must have previously been fetched through [`lookahead`]
486 ///
487 /// # Return
488 /// Returns true if the character is [a flow], false otherwise.
489 ///
490 /// [`lookahead`]: Input::lookahead
491 /// [a flow]: is_flow
492 #[inline]
493 fn next_is_flow(&self) -> bool {
494 is_flow(self.peek())
495 }
496
497 /// Check whether the next character is [a digit].
498 ///
499 /// The character must have previously been fetched through [`lookahead`]
500 ///
501 /// # Return
502 /// Returns true if the character is [a digit], false otherwise.
503 ///
504 /// [`lookahead`]: Input::lookahead
505 /// [a digit]: is_digit
506 #[inline]
507 fn next_is_digit(&self) -> bool {
508 is_digit(self.peek())
509 }
510
511 /// Check whether the next character is [a letter].
512 ///
513 /// The character must have previously been fetched through [`lookahead`]
514 ///
515 /// # Return
516 /// Returns true if the character is [a letter], false otherwise.
517 ///
518 /// [`lookahead`]: Input::lookahead
519 /// [a letter]: is_alpha
520 #[inline]
521 fn next_is_alpha(&self) -> bool {
522 is_alpha(self.peek())
523 }
524
525 /// Skip printable characters until a [breakz] or non-printable character is found.
526 ///
527 /// The stopping character is not consumed.
528 ///
529 /// # Return
530 /// Return the number of characters that were consumed. The number of characters returned can
531 /// be used to advance the index and column, since no end-of-line character will be consumed.
532 ///
533 /// [breakz]: is_breakz
534 #[inline]
535 fn skip_while_non_breakz(&mut self) -> usize {
536 let mut count = 0;
537 while {
538 let c = self.look_ch();
539 !is_breakz(c) && crate::char_traits::is_printable(c)
540 } {
541 count += 1;
542 self.skip();
543 }
544 count
545 }
546
547 /// Skip characters from the input while [blanks] are found.
548 ///
549 /// The characters are consumed from the input.
550 ///
551 /// # Return
552 /// Return the number of characters that were consumed. The number of characters returned can
553 /// be used to advance the index and column, since no end-of-line character will be consumed.
554 ///
555 /// [blanks]: is_blank
556 fn skip_while_blank(&mut self) -> usize {
557 let mut n_bytes = 0;
558 while is_blank(self.look_ch()) {
559 n_bytes += self.peek().len_utf8();
560 self.skip();
561 }
562 n_bytes
563 }
564
565 /// Fetch characters from the input while we encounter letters and store them in `out`.
566 ///
567 /// The characters are consumed from the input.
568 ///
569 /// # Return
570 /// Return the number of characters that were consumed. The number of characters returned can
571 /// be used to advance the index and column, since no end-of-line character will be consumed.
572 fn fetch_while_is_alpha(&mut self, out: &mut String) -> usize {
573 let mut n_bytes = 0;
574 while is_alpha(self.look_ch()) {
575 let c = self.peek();
576 n_bytes += c.len_utf8();
577 out.push(c);
578 self.skip();
579 }
580 n_bytes
581 }
582
583 /// Fetch characters as long as they satisfy `is_yaml_non_space(c)`.
584 ///
585 /// The characters are consumed from the input.
586 ///
587 /// # Return
588 /// Return the number of characters that were consumed. The number of characters returned can
589 /// be used to advance the index and column, since no end-of-line character will be consumed.
590 fn fetch_while_is_yaml_non_space(&mut self, out: &mut String) -> usize {
591 let mut chars_consumed = 0;
592 loop {
593 let c = self.look_ch();
594 if !crate::char_traits::is_yaml_non_space(c) || is_z(c) {
595 break;
596 }
597 let c = self.peek();
598 out.push(c);
599 self.skip();
600 chars_consumed += 1;
601 }
602 chars_consumed
603 }
604
605 /// Fetch a chunk of plain scalar characters.
606 ///
607 /// This optimization method allows the input to batch process characters.
608 /// Returns (stopped, `chars_consumed`).
609 /// stopped is true if the chunk ended because of a non-plain-scalar character.
610 fn fetch_plain_scalar_chunk(
611 &mut self,
612 out: &mut String,
613 count: usize,
614 flow_level_gt_0: bool,
615 ) -> (bool, usize) {
616 let mut chars_consumed = 0;
617 for _ in 0..count {
618 self.lookahead(1);
619 if self.next_is_blank_or_breakz() || !self.next_can_be_plain_scalar(flow_level_gt_0) {
620 return (true, chars_consumed);
621 }
622 out.push(self.peek());
623 self.skip();
624 chars_consumed += 1;
625 }
626 (false, chars_consumed)
627 }
628
629 /// Consume a chunk of plain-scalar characters without materializing them.
630 ///
631 /// Stable inputs use this while the scanner retains the source as a borrowed slice and only
632 /// promotes it to an owned buffer if YAML folding changes the scalar contents.
633 fn skip_plain_scalar_chunk(&mut self, count: usize, flow_level_gt_0: bool) -> (bool, usize) {
634 let mut chars_consumed = 0;
635 for _ in 0..count {
636 self.lookahead(1);
637 if self.next_is_blank_or_breakz() || !self.next_can_be_plain_scalar(flow_level_gt_0) {
638 return (true, chars_consumed);
639 }
640 self.skip();
641 chars_consumed += 1;
642 }
643 (false, chars_consumed)
644 }
645}
646
647/// Behavior to adopt regarding treating tabs as whitespace.
648///
649/// Although tab is valid YAML whitespace, it does not always behave the same as a space.
650#[derive(Copy, Clone, Eq, PartialEq)]
651pub enum SkipTabs {
652 /// Skip all tabs as whitespace.
653 Yes,
654 /// Don't skip any tab. Return from the function when encountering one.
655 No,
656 /// Return value from the function.
657 Result(
658 /// Whether tabs were encountered.
659 bool,
660 /// Whether at least one valid YAML whitespace character has been encountered.
661 bool,
662 ),
663}
664
665impl SkipTabs {
666 /// Whether tabs were found while skipping whitespace.
667 ///
668 /// This function must be called after a call to `skip_ws_to_eol`.
669 #[must_use]
670 pub fn found_tabs(self) -> bool {
671 matches!(self, SkipTabs::Result(true, _))
672 }
673
674 /// Whether a valid YAML whitespace has been found in skipped-over content.
675 ///
676 /// This function must be called after a call to `skip_ws_to_eol`.
677 #[must_use]
678 pub fn has_valid_yaml_ws(self) -> bool {
679 matches!(self, SkipTabs::Result(_, true))
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use super::{Input, SkipTabs};
686 use crate::error::ErrorKind;
687
688 struct MinimalInput;
689
690 impl Input for MinimalInput {
691 fn lookahead(&mut self, _count: usize) {}
692
693 fn buflen(&self) -> usize {
694 0
695 }
696
697 fn bufmaxlen(&self) -> usize {
698 0
699 }
700
701 fn raw_read_ch(&mut self) -> char {
702 '\0'
703 }
704
705 fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
706 None
707 }
708
709 fn skip(&mut self) {}
710
711 fn skip_n(&mut self, _count: usize) {}
712
713 fn peek(&self) -> char {
714 '\0'
715 }
716
717 fn peek_nth(&self, _n: usize) -> char {
718 '\0'
719 }
720 }
721
722 #[test]
723 fn default_slice_bytes_returns_none() {
724 let mut input = MinimalInput;
725
726 input.lookahead(4);
727 assert_eq!(input.buflen(), 0);
728 assert_eq!(input.bufmaxlen(), 0);
729 assert_eq!(input.raw_read_ch(), '\0');
730 assert_eq!(input.raw_read_non_breakz_ch(), None);
731 input.skip();
732 input.skip_n(2);
733 assert_eq!(input.peek(), '\0');
734 assert_eq!(input.peek_nth(1), '\0');
735 assert_eq!(input.byte_offset(), None);
736 assert_eq!(input.slice_bytes(0, 0), None);
737 }
738
739 #[test]
740 fn default_skip_ws_to_eol_rejects_unseparated_comment() {
741 let mut input = super::buffered::BufferedInput::new("#comment\n".chars());
742
743 let (consumed, result) = input.skip_ws_to_eol(SkipTabs::Yes);
744
745 assert_eq!(consumed, 0);
746 assert_eq!(result.err(), Some(ErrorKind::CommentNotSeparated));
747 assert_eq!(input.peek(), '#');
748 }
749}