Skip to main content

granit_parser/
scanner.rs

1//! Home to the YAML Scanner.
2//!
3//! The scanner is the lowest-level parsing utility. It is the lexer / tokenizer, reading input a
4//! character at a time and emitting tokens that can later be interpreted by the [`crate::parser`]
5//! to check for more context and validity.
6//!
7//! Due to the grammar of YAML, the scanner has to have some context and is not error-free.
8
9#![allow(clippy::cast_possible_wrap)]
10#![allow(clippy::cast_sign_loss)]
11
12use alloc::{
13    borrow::{Cow, ToOwned},
14    collections::VecDeque,
15    string::String,
16    vec::Vec,
17};
18use core::char;
19
20use crate::{
21    char_traits::{
22        as_hex, is_anchor_char, is_blank_or_breakz, is_bom, is_break, is_breakz, is_flow, is_hex,
23        is_printable, is_tag_char, is_uri_char,
24    },
25    error::{ErrorKind, ScanError},
26    input::{BorrowedInput, SkipTabs},
27};
28
29/// Maximum number of characters the scanner may look ahead while disambiguating a simple key.
30const SIMPLE_KEY_MAX_LOOKAHEAD: usize = 1024;
31
32/// The source style used for a YAML scalar.
33#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
34pub enum ScalarStyle {
35    /// A YAML plain scalar.
36    Plain,
37    /// A YAML single quoted scalar.
38    SingleQuoted,
39    /// A YAML double quoted scalar.
40    DoubleQuoted,
41
42    /// A YAML literal block (`|` block).
43    ///
44    /// See [8.1.2](https://yaml.org/spec/1.2.2/#812-literal-style).
45    /// In literal blocks, any indented character is content, including white space characters.
46    /// There is no way to escape characters, nor to break a long line.
47    Literal,
48    /// A YAML folded block (`>` block).
49    ///
50    /// See [8.1.3](https://yaml.org/spec/1.2.2/#813-folded-style).
51    /// In folded blocks, any indented character is content, including white space characters.
52    /// There is no way to escape characters. Content is subject to line folding, allowing breaking
53    /// long lines.
54    Folded,
55}
56
57/// Offset information for a [`Marker`].
58///
59/// YAML inputs can come from either a full `&str` (stable backing storage) or a streaming
60/// character source. For stable inputs, we can track both a character index and a byte offset.
61/// For streaming inputs, byte offsets are not generally useful (and may not correspond to any
62/// meaningful underlying file/source), so they are optional.
63#[derive(Clone, Copy, Debug, Default)]
64struct MarkerOffsets {
65    /// The index (in characters) in the source.
66    chars: usize,
67    /// The offset (in bytes) in the source, if available.
68    bytes: Option<usize>,
69}
70
71impl PartialEq for MarkerOffsets {
72    fn eq(&self, other: &Self) -> bool {
73        // Byte offsets are an optional diagnostic enhancement and may differ between input
74        // backends (e.g., `&str` vs streaming). Equality is therefore based on the character
75        // position only.
76        self.chars == other.chars
77    }
78}
79
80impl Eq for MarkerOffsets {}
81
82/// A location in a YAML document.
83#[derive(Clone, Copy, PartialEq, Debug, Eq, Default)]
84pub struct Marker {
85    /// Offsets in the source.
86    offsets: MarkerOffsets,
87    /// The line (1-indexed).
88    line: usize,
89    /// The column (0-indexed).
90    col: usize,
91}
92
93impl Marker {
94    /// Create a new [`Marker`] at the given position.
95    #[must_use]
96    pub fn new(index: usize, line: usize, col: usize) -> Marker {
97        Marker {
98            offsets: MarkerOffsets {
99                chars: index,
100                bytes: None,
101            },
102            line,
103            col,
104        }
105    }
106
107    /// Return a copy of the marker with the given optional byte offset.
108    #[must_use]
109    pub fn with_byte_offset(mut self, byte_offset: Option<usize>) -> Marker {
110        self.offsets.bytes = byte_offset;
111        self
112    }
113
114    /// Return the index (in characters) of the marker in the source.
115    #[must_use]
116    pub fn index(&self) -> usize {
117        self.offsets.chars
118    }
119
120    /// Return the byte offset of the marker in the source, if available.
121    #[must_use]
122    pub fn byte_offset(&self) -> Option<usize> {
123        self.offsets.bytes
124    }
125
126    /// Return the line of the marker in the source.
127    #[must_use]
128    pub fn line(&self) -> usize {
129        self.line
130    }
131
132    /// Return the column of the marker in the source.
133    #[must_use]
134    pub fn col(&self) -> usize {
135        self.col
136    }
137}
138
139/// A range of locations in a YAML document.
140#[derive(Clone, Copy, PartialEq, Debug, Eq, Default)]
141pub struct Span {
142    /// The start (inclusive) of the range.
143    pub start: Marker,
144    /// The end (exclusive) of the range.
145    pub end: Marker,
146
147    /// Optional indentation hint associated with this span.
148    ///
149    /// This is only meaningful for certain parser-emitted events (notably: block mapping keys).
150    /// When indentation is not meaningful or cannot be provided, it must be `None`.
151    pub indent: Option<usize>,
152
153    /// Optional source marker for the explicit tag token attached to this node.
154    ///
155    /// This is only meaningful for parser-emitted node events that carry a resolved tag, such as
156    /// [`Event::Scalar`](crate::Event::Scalar),
157    /// [`Event::SequenceStart`](crate::Event::SequenceStart), or
158    /// [`Event::MappingStart`](crate::Event::MappingStart). The normal [`Span::start`] and
159    /// [`Span::end`] continue to cover the node value or collection; `tag_start` points to the
160    /// tag token when that token appears at a different source location.
161    pub tag_start: Option<Marker>,
162}
163
164impl Span {
165    /// Create a new [`Span`] for the given range.
166    #[must_use]
167    pub fn new(start: Marker, end: Marker) -> Span {
168        Span {
169            start,
170            end,
171            indent: None,
172            tag_start: None,
173        }
174    }
175
176    /// Create an empty [`Span`] at a given location.
177    ///
178    /// An empty span doesn't contain any characters, but its position may still be meaningful.
179    /// For example, for an indented sequence [`SequenceEnd`] has a location but an empty span.
180    ///
181    /// [`SequenceEnd`]: crate::Event::SequenceEnd
182    #[must_use]
183    pub fn empty(mark: Marker) -> Span {
184        Span {
185            start: mark,
186            end: mark,
187            indent: None,
188            tag_start: None,
189        }
190    }
191
192    /// Return a copy of this [`Span`] with the given indentation hint.
193    #[must_use]
194    pub fn with_indent(mut self, indent: Option<usize>) -> Span {
195        self.indent = indent;
196        self
197    }
198
199    /// Return a copy of this [`Span`] with the given explicit tag-token start marker.
200    #[must_use]
201    pub fn with_tag_start(mut self, tag_start: Option<Marker>) -> Span {
202        self.tag_start = tag_start;
203        self
204    }
205
206    /// Return the source marker of the explicit tag token attached to this node, if any.
207    ///
208    /// The regular span still covers the node value or collection. This accessor is useful for
209    /// diagnostics that should point at the tag itself, especially when a tagged block collection
210    /// begins on a later line than the tag token.
211    #[must_use]
212    pub fn tag_start(&self) -> Option<Marker> {
213        self.tag_start
214    }
215
216    /// Return the length of the span (in characters).
217    #[must_use]
218    pub fn len(&self) -> usize {
219        self.end.index() - self.start.index()
220    }
221
222    /// Return whether the [`Span`] has a length of zero.
223    #[must_use]
224    pub fn is_empty(&self) -> bool {
225        self.len() == 0
226    }
227
228    /// Return the byte range of the span, if available.
229    #[must_use]
230    pub fn byte_range(&self) -> Option<core::ops::Range<usize>> {
231        let start = self.start.byte_offset()?;
232        let end = self.end.byte_offset()?;
233        Some(start..end)
234    }
235
236    /// Return the source text covered by this span, if byte offsets are available
237    /// and the range is valid for the provided input.
238    #[must_use]
239    pub fn slice<'source>(&self, source: &'source str) -> Option<&'source str> {
240        source.get(self.byte_range()?)
241    }
242}
243
244/// A positional hint for a YAML source comment.
245///
246/// The parser currently recognizes these placements:
247///
248/// ```yaml
249/// # Above
250/// key: value # Right
251///
252/// # Free
253///
254/// next: value
255///
256/// # Last
257/// ```
258#[non_exhaustive]
259#[derive(Clone, Copy, PartialEq, Debug, Eq, Default)]
260pub enum Placement {
261    /// An own-line comment immediately before another YAML token.
262    ///
263    /// This usually means the comment visually describes the following node.
264    /// Consecutive own-line comments without blank lines between them are also considered
265    /// `Above`, so a comment block can attach to the next YAML element as a group.
266    Above,
267    /// A same-line comment after YAML content or syntax. Examples include `key: value # Right`
268    /// and `- # Right` for an empty sequence entry.
269    Right,
270    /// A standalone own-line comment that is separated from nearby YAML tokens.
271    ///
272    /// This is the fallback for comments that are neither same-line comments, immediately above a
273    /// following token, nor the final comment in the stream. Consumers should treat `Free` as not
274    /// having an obvious neighboring node.
275    #[default]
276    Free,
277    /// An own-line comment at the end of the input stream.
278    ///
279    /// A `Last` comment may be followed by blank lines, but no further YAML token appears before
280    /// `StreamEnd`.
281    Last,
282}
283
284/// YAML comment metadata captured from the source.
285///
286/// Comments are presentation metadata, not YAML data. This type carries the raw comment payload and
287/// a best-effort [`Placement`] hint. The companion [`Token`] carries the comment's source span.
288#[derive(Clone, PartialEq, Debug, Eq)]
289pub struct Comment<'input> {
290    /// Raw comment payload exactly after `#`, excluding only the line break.
291    ///
292    /// Leading spaces are preserved, including a single space immediately after `#` when present.
293    text: Cow<'input, str>,
294    /// Best-effort placement of this comment relative to nearby YAML content.
295    placement: Placement,
296}
297
298impl<'input> Comment<'input> {
299    /// Create captured YAML comment metadata from a raw payload.
300    ///
301    /// The placement defaults to [`Placement::Free`]. Use [`Comment::with_placement`] when the
302    /// caller already knows a more specific placement.
303    #[must_use]
304    pub fn new(text: impl Into<Cow<'input, str>>) -> Self {
305        Self {
306            text: text.into(),
307            placement: Placement::Free,
308        }
309    }
310
311    /// Return this comment with the given placement.
312    #[must_use]
313    pub fn with_placement(mut self, placement: Placement) -> Self {
314        self.placement = placement;
315        self
316    }
317
318    /// Return the raw comment payload exactly after `#`, excluding only the line break.
319    #[must_use]
320    pub fn text(&self) -> &str {
321        self.text.as_ref()
322    }
323
324    /// Return the best-effort placement of this comment relative to nearby YAML content.
325    #[must_use]
326    pub const fn placement(&self) -> Placement {
327        self.placement
328    }
329
330    /// Consume the comment and return its raw payload without cloning.
331    #[must_use]
332    pub fn into_text(self) -> Cow<'input, str> {
333        self.text
334    }
335
336    /// Return the comment payload with surrounding whitespace removed.
337    ///
338    /// This helper is ergonomic only. The raw [`Self::text`] payload remains unchanged.
339    #[must_use]
340    pub fn trimmed_text(&self) -> &str {
341        self.text.trim()
342    }
343}
344
345impl AsRef<str> for Comment<'_> {
346    fn as_ref(&self) -> &str {
347        self.text.as_ref()
348    }
349}
350
351/// The contents of a scanner token.
352#[non_exhaustive]
353#[derive(Clone, PartialEq, Debug, Eq)]
354pub enum TokenType<'input> {
355    /// The start of the stream. Sent first, before even [`TokenType::DocumentStart`].
356    StreamStart,
357    /// The end of the stream, EOF.
358    StreamEnd,
359    /// A YAML version directive.
360    VersionDirective(
361        /// Major version number.
362        u32,
363        /// Minor version number.
364        u32,
365    ),
366    /// A YAML tag directive (e.g.: `!!str`, `!foo!bar`, ...).
367    TagDirective(
368        /// Tag directive handle, such as `!` or `!app!`.
369        Cow<'input, str>,
370        /// Tag URI prefix associated with the handle.
371        Cow<'input, str>,
372    ),
373    /// The start of a YAML document (`---`).
374    DocumentStart,
375    /// The end of a YAML document (`...`).
376    DocumentEnd,
377    /// The start of a sequence block.
378    ///
379    /// Sequence blocks are arrays starting with a `-`.
380    BlockSequenceStart,
381    /// The start of a block mapping.
382    ///
383    /// Block mappings are key-value collections written with `key: value` entries.
384    BlockMappingStart,
385    /// End of the corresponding `BlockSequenceStart` or `BlockMappingStart`.
386    BlockEnd,
387    /// Start of an inline sequence (`[ a, b ]`).
388    FlowSequenceStart,
389    /// End of an inline sequence.
390    FlowSequenceEnd,
391    /// Start of an inline mapping (`{ a: b, c: d }`).
392    FlowMappingStart,
393    /// End of an inline mapping.
394    FlowMappingEnd,
395    /// An entry in a block sequence (see [`TokenType::BlockSequenceStart`]).
396    BlockEntry,
397    /// An entry in a flow sequence (see [`TokenType::FlowSequenceStart`]).
398    FlowEntry,
399    /// A key in a mapping.
400    Key,
401    /// A value in a mapping.
402    Value,
403    /// A reference to a previously defined anchor.
404    Alias(Cow<'input, str>),
405    /// A YAML anchor definition introduced by `&`.
406    Anchor(Cow<'input, str>),
407    /// A YAML tag (starting with bangs `!`).
408    Tag(
409        /// The handle of the tag.
410        Cow<'input, str>,
411        /// The suffix of the tag.
412        Cow<'input, str>,
413    ),
414    /// A regular YAML scalar.
415    Scalar(ScalarStyle, Cow<'input, str>),
416    /// A YAML source comment.
417    ///
418    /// The token payload carries the raw text exactly after `#` and an initial [`Placement`] hint.
419    /// The companion [`Token`] span covers the whole source comment, including `#` and excluding the
420    /// line break.
421    Comment(
422        /// Captured comment metadata.
423        Comment<'input>,
424    ),
425    /// A reserved YAML directive.
426    ReservedDirective(
427        /// Directive name.
428        String,
429        /// Directive parameters, split on YAML whitespace.
430        Vec<String>,
431    ),
432}
433
434/// A scanner token.
435#[derive(Clone, PartialEq, Debug, Eq)]
436pub struct Token<'input>(Span, TokenType<'input>);
437
438impl<'input> Token<'input> {
439    /// Create a scanner token from its source span and payload.
440    #[must_use]
441    pub const fn new(span: Span, token_type: TokenType<'input>) -> Self {
442        Self(span, token_type)
443    }
444
445    /// Return the source span covered by this token.
446    #[must_use]
447    pub const fn span(&self) -> Span {
448        self.0
449    }
450
451    /// Return the payload emitted by the scanner.
452    #[must_use]
453    pub const fn token_type(&self) -> &TokenType<'input> {
454        &self.1
455    }
456
457    /// Consume the token and return its source span and payload.
458    #[must_use]
459    pub fn into_parts(self) -> (Span, TokenType<'input>) {
460        (self.0, self.1)
461    }
462}
463
464/// Token payload used in the scanner's internal queue.
465///
466/// Public [`Token`] values are reconstructed when the scanner emits them.
467#[derive(Clone, PartialEq, Debug, Eq)]
468pub(crate) enum QueuedTokenType<'input> {
469    StreamStart,
470    StreamEnd,
471    VersionDirective(u32, u32),
472    TagDirective(Cow<'input, str>, Cow<'input, str>),
473    DocumentStart,
474    DocumentEnd,
475    BlockSequenceStart,
476    BlockMappingStart,
477    BlockEnd,
478    FlowSequenceStart,
479    FlowSequenceEnd,
480    FlowMappingStart,
481    FlowMappingEnd,
482    BlockEntry,
483    FlowEntry,
484    Key,
485    Value,
486    Alias(Cow<'input, str>),
487    Anchor(Cow<'input, str>),
488    Tag(Cow<'input, str>, Cow<'input, str>),
489    Scalar(ScalarStyle, Cow<'input, str>),
490    Comment(Comment<'input>),
491    ReservedDirective(String, Vec<String>),
492}
493
494impl<'input> QueuedTokenType<'input> {
495    fn into_public(self) -> TokenType<'input> {
496        match self {
497            Self::StreamStart => TokenType::StreamStart,
498            Self::StreamEnd => TokenType::StreamEnd,
499            Self::VersionDirective(major, minor) => TokenType::VersionDirective(major, minor),
500            Self::TagDirective(handle, prefix) => TokenType::TagDirective(handle, prefix),
501            Self::DocumentStart => TokenType::DocumentStart,
502            Self::DocumentEnd => TokenType::DocumentEnd,
503            Self::BlockSequenceStart => TokenType::BlockSequenceStart,
504            Self::BlockMappingStart => TokenType::BlockMappingStart,
505            Self::BlockEnd => TokenType::BlockEnd,
506            Self::FlowSequenceStart => TokenType::FlowSequenceStart,
507            Self::FlowSequenceEnd => TokenType::FlowSequenceEnd,
508            Self::FlowMappingStart => TokenType::FlowMappingStart,
509            Self::FlowMappingEnd => TokenType::FlowMappingEnd,
510            Self::BlockEntry => TokenType::BlockEntry,
511            Self::FlowEntry => TokenType::FlowEntry,
512            Self::Key => TokenType::Key,
513            Self::Value => TokenType::Value,
514            Self::Alias(name) => TokenType::Alias(name),
515            Self::Anchor(name) => TokenType::Anchor(name),
516            Self::Tag(handle, suffix) => TokenType::Tag(handle, suffix),
517            Self::Scalar(style, value) => TokenType::Scalar(style, value),
518            Self::Comment(comment) => TokenType::Comment(comment),
519            Self::ReservedDirective(name, params) => TokenType::ReservedDirective(name, params),
520        }
521    }
522}
523
524impl<'input> From<TokenType<'input>> for QueuedTokenType<'input> {
525    fn from(token: TokenType<'input>) -> Self {
526        match token {
527            TokenType::StreamStart => Self::StreamStart,
528            TokenType::StreamEnd => Self::StreamEnd,
529            TokenType::VersionDirective(major, minor) => Self::VersionDirective(major, minor),
530            TokenType::TagDirective(handle, prefix) => Self::TagDirective(handle, prefix),
531            TokenType::DocumentStart => Self::DocumentStart,
532            TokenType::DocumentEnd => Self::DocumentEnd,
533            TokenType::BlockSequenceStart => Self::BlockSequenceStart,
534            TokenType::BlockMappingStart => Self::BlockMappingStart,
535            TokenType::BlockEnd => Self::BlockEnd,
536            TokenType::FlowSequenceStart => Self::FlowSequenceStart,
537            TokenType::FlowSequenceEnd => Self::FlowSequenceEnd,
538            TokenType::FlowMappingStart => Self::FlowMappingStart,
539            TokenType::FlowMappingEnd => Self::FlowMappingEnd,
540            TokenType::BlockEntry => Self::BlockEntry,
541            TokenType::FlowEntry => Self::FlowEntry,
542            TokenType::Key => Self::Key,
543            TokenType::Value => Self::Value,
544            TokenType::Alias(name) => Self::Alias(name),
545            TokenType::Anchor(name) => Self::Anchor(name),
546            TokenType::Tag(handle, suffix) => Self::Tag(handle, suffix),
547            TokenType::Scalar(style, value) => Self::Scalar(style, value),
548            TokenType::Comment(comment) => Self::Comment(comment),
549            TokenType::ReservedDirective(name, params) => Self::ReservedDirective(name, params),
550        }
551    }
552}
553
554/// A compact token stored by the scanner before it is emitted publicly.
555#[derive(Clone, PartialEq, Debug, Eq)]
556pub(crate) struct QueuedToken<'input>(pub(crate) Span, pub(crate) QueuedTokenType<'input>);
557
558impl<'input> QueuedToken<'input> {
559    fn into_public(self) -> Token<'input> {
560        Token(self.0, self.1.into_public())
561    }
562}
563
564impl<'input> From<Token<'input>> for QueuedToken<'input> {
565    fn from(token: Token<'input>) -> Self {
566        Self(token.0, token.1.into())
567    }
568}
569
570/// A scalar that was parsed and may correspond to a simple key.
571///
572/// Upon scanning the following YAML:
573/// ```yaml
574/// a: b
575/// ```
576/// We do not know that `a` is a key for a map until we have reached the following `:`. For this
577/// YAML, we would store `a` as a scalar token in the [`Scanner`], but not emit it yet. It would be
578/// kept inside the scanner until more context is fetched and we are able to know whether it is a
579/// plain scalar or a key.
580///
581/// For example, see the following two YAML documents:
582/// ```yaml
583/// ---
584/// a: b # Here, `a` is a key.
585/// ...
586/// ---
587/// a # Here, `a` is a plain scalar.
588/// ...
589/// ```
590/// An instance of [`SimpleKey`] is created in the [`Scanner`] when such ambiguity occurs.
591///
592/// In both documents, scanning `a` would lead to the creation of a [`SimpleKey`] with
593/// [`Self::possible`] set to `true`. The token for `a` would be pushed in the [`Scanner`] but not
594/// yet emitted. Instead, more context would be fetched (through [`Scanner::fetch_more_tokens`]).
595///
596/// In the first document, upon reaching the `:`, the [`SimpleKey`] would be inspected and our
597/// scalar `a` since it is a possible key, would be "turned" into a key. This is done by prepending
598/// a [`TokenType::Key`] to our scalar token in the [`Scanner`]. This way, the
599/// [`crate::parser::Parser`] would read the [`TokenType::Key`] token before the
600/// [`TokenType::Scalar`] token.
601///
602/// In the second document however, reaching EOF would mark the [`SimpleKey`] as no longer possible,
603/// and no [`TokenType::Key`] would be emitted by the scanner.
604#[derive(Clone, Copy, PartialEq, Debug, Eq)]
605struct SimpleKey {
606    /// Whether the token this [`SimpleKey`] refers to may still be a key.
607    ///
608    /// Sometimes, when we have more context, we notice that what we thought could be a key no
609    /// longer can be. In that case, [`Self::possible`] is set to `false`.
610    ///
611    /// For instance, let us consider the following invalid YAML:
612    /// ```yaml
613    /// key
614    ///   : value
615    /// ```
616    /// Upon reading the `\n` after `key`, the [`SimpleKey`] that was created for `key` is no longer
617    /// possible and [`Self::possible`] is set to `false`.
618    possible: bool,
619    /// Whether the token this [`SimpleKey`] refers to is required to be a key.
620    ///
621    /// With more context, we may know for sure that the token must be a key. If later input makes
622    /// that impossible, the scanner must report an error instead of silently treating the token as a
623    /// plain scalar.
624    ///
625    /// This happens for simple keys at the current block indentation where the surrounding
626    /// collection requires the next token to be a mapping key.
627    required: bool,
628    /// The index of the token referred to by the [`SimpleKey`].
629    ///
630    /// This is the index in the scanner, which takes into account both the tokens that have been
631    /// emitted and those about to be emitted. See [`Scanner::tokens_parsed`] and
632    /// [`Scanner::tokens`] for more details.
633    token_number: usize,
634    /// The position at which the token the [`SimpleKey`] refers to is.
635    mark: Marker,
636}
637
638impl SimpleKey {
639    /// Create a new [`SimpleKey`] at the given `Marker` and with the given flow level.
640    fn new(mark: Marker) -> SimpleKey {
641        SimpleKey {
642            possible: false,
643            required: false,
644            token_number: 0,
645            mark,
646        }
647    }
648}
649
650/// An indentation level on the stack of indentations.
651#[derive(Clone, Debug, Default)]
652struct Indent {
653    /// The former indentation level.
654    indent: isize,
655    /// Whether, upon closing, this indents generates a `BlockEnd` token.
656    ///
657    /// There are levels of indentation which do not start a block. Examples of this would be:
658    /// ```yaml
659    /// -
660    ///   foo # ok
661    /// -
662    /// bar # ko, bar needs to be indented further than the `-`.
663    /// - [
664    ///  baz, # ok
665    /// quux # ko, quux needs to be indented further than the '-'.
666    /// ] # ko, the closing bracket needs to be indented further than the `-`.
667    /// ```
668    ///
669    /// The indentation level created by the `-` is for a single entry in the sequence. Emitting a
670    /// `BlockEnd` when this indentation block ends would generate one `BlockEnd` per entry in the
671    /// sequence, although we must have exactly one to end the sequence.
672    needs_block_end: bool,
673}
674
675/// The knowledge we have about an implicit mapping.
676///
677/// Implicit mappings occur in flow sequences where the opening `{` for a mapping in a flow
678/// sequence is omitted:
679/// ```yaml
680/// [ a: b, c: d ]
681/// # Equivalent to
682/// [ { a: b }, { c: d } ]
683/// # Equivalent to
684/// - a: b
685/// - c: d
686/// ```
687///
688/// The state must be carefully tracked for each nested flow sequence since we must emit a
689/// [`FlowMappingStart`] event when encountering `a` and `c` in our previous example without a
690/// character hinting us. Similarly, we must emit a [`FlowMappingEnd`] event when we reach the `,`
691/// or the `]`. If the state is not properly tracked, we may omit to emit these events or emit them
692/// out-of-order.
693///
694/// [`FlowMappingStart`]: TokenType::FlowMappingStart
695/// [`FlowMappingEnd`]: TokenType::FlowMappingEnd
696#[derive(Debug, PartialEq)]
697enum ImplicitMappingState {
698    /// It is possible there is an implicit mapping.
699    ///
700    /// This state is the one when we have just encountered the opening `[`. We need more context
701    /// to know whether an implicit mapping follows.
702    Possible,
703    /// We are inside the implicit mapping.
704    ///
705    /// Note that this state is not set immediately (we need to have encountered the `:` to know).
706    Inside(u8),
707}
708
709/// The YAML scanner.
710///
711/// This corresponds to the low-level interface when reading YAML. The scanner emits tokens as they
712/// are read (akin to a lexer), but it also holds sufficient context to be able to disambiguate
713/// some of the constructs. It has understanding of indentation and whitespace and is able to
714/// generate error messages for some invalid YAML constructs.
715///
716/// It is however not a full parser and needs [`crate::parser::Parser`] to fully detect invalid
717/// YAML documents.
718///
719/// `Scanner` is a fallible iterator over [`Token`] values. A scanning failure is emitted as one
720/// [`Err`] item, after which the iterator is exhausted.
721#[derive(Debug)]
722#[allow(clippy::struct_excessive_bools)]
723pub struct Scanner<'input, T> {
724    /// The input source.
725    ///
726    /// This must implement [`Input`].
727    input: T,
728    /// The position of the cursor within the reader.
729    mark: Marker,
730    /// Buffer for tokens to be returned.
731    ///
732    /// This buffer can hold some temporary tokens that are not yet ready to be returned. For
733    /// instance, if we just read a scalar, it can be a value or a key if an implicit mapping
734    /// follows. In this case, the token stays in the `VecDeque` but cannot be returned from
735    /// [`Self::next`] until we have more context.
736    tokens: VecDeque<QueuedToken<'input>>,
737    /// Whether a terminal error has been emitted by the iterator.
738    failed: bool,
739    /// Error found after one or more already-scanned comment tokens.
740    deferred_error: Option<ScanError>,
741    /// Whether the input may contain `#` comment indicators.
742    comments_possible: bool,
743
744    /// Whether we have already emitted the `StreamStart` token.
745    stream_start_produced: bool,
746    /// Whether we have already emitted the `StreamEnd` token.
747    stream_end_produced: bool,
748    /// Whether the scanner is still in the prefix of the next document.
749    ///
750    /// A BOM may appear in a document prefix, before directives/comments/content. Once a document
751    /// start marker or any content token is scanned, another BOM is document content and must be
752    /// rejected unless it appears inside a quoted scalar.
753    document_prefix_allowed: bool,
754    /// In some flow contexts, the value of a mapping is allowed to be adjacent to the `:`. When it
755    /// is, the index at which the `:` may be must be stored in `adjacent_value_allowed_at`.
756    adjacent_value_allowed_at: usize,
757    /// Whether a simple key could potentially start at the current position.
758    ///
759    /// Simple keys are the opposite of complex keys which are keys starting with `?`.
760    simple_key_allowed: bool,
761    /// A stack of potential simple keys.
762    ///
763    /// Refer to the documentation of [`SimpleKey`] for a more in-depth explanation of what they
764    /// are.
765    simple_keys: smallvec::SmallVec<[SimpleKey; 8]>,
766    /// The current indentation level.
767    indent: isize,
768    /// List of all block indentation levels we are in (except the current one).
769    indents: smallvec::SmallVec<[Indent; 8]>,
770    /// Level of nesting of flow sequences.
771    flow_level: u8,
772    /// The number of tokens that have been returned from the scanner.
773    ///
774    /// This excludes the tokens from [`Self::tokens`].
775    tokens_parsed: usize,
776    /// Whether a token is ready to be taken from [`Self::tokens`].
777    token_available: bool,
778    /// Whether all characters encountered since the last newline were whitespace.
779    leading_whitespace: bool,
780    /// Whether we started a flow mapping at each flow nesting level.
781    ///
782    /// This is used to detect implicit flow mapping starts such as:
783    /// ```yaml
784    /// [ : foo ] # { null: "foo" }
785    /// ```
786    flow_mapping_started: smallvec::SmallVec<[bool; 8]>,
787    /// An array of states, representing whether flow sequences have implicit mappings.
788    ///
789    /// When a flow mapping is possible (when encountering the first `[` or a `,` in a sequence),
790    /// the state is set to [`Possible`].
791    /// When we encounter the `:`, we know we are in an implicit mapping and can set the state to
792    /// [`Inside`].
793    ///
794    /// There is one entry in this [`Vec`] for each nested flow sequence that we are in.
795    /// The entries are created with the opening `[` and popped with the closing `]`.
796    ///
797    /// [`Possible`]: ImplicitMappingState::Possible
798    /// [`Inside`]: ImplicitMappingState::Inside
799    implicit_flow_mapping_states: smallvec::SmallVec<[ImplicitMappingState; 8]>,
800    /// If a plain scalar was terminated by a `#` comment on its line, we set this
801    /// to detect an illegal multiline continuation on the following line.
802    interrupted_plain_by_comment: Option<Marker>,
803    /// Whether the scanner is still validating whitespace after an explicit `?` key indicator.
804    ///
805    /// This stays set across streamed comment tokens so a tab after the comment run is rejected the
806    /// same way it was when that whitespace was scanned in one pass.
807    explicit_key_tab_check_pending: bool,
808    /// A stack of markers for opening brackets `[` and `{`.
809    flow_markers: smallvec::SmallVec<[(Marker, char); 8]>,
810    buf_leading_break: String,
811    buf_trailing_breaks: String,
812    buf_whitespaces: String,
813}
814
815impl<'input, T: BorrowedInput<'input>> Iterator for Scanner<'input, T> {
816    type Item = Result<Token<'input>, ScanError>;
817
818    fn next(&mut self) -> Option<Self::Item> {
819        if self.failed {
820            return None;
821        }
822        match self.next_token() {
823            Ok(Some(tok)) => {
824                debug_print!(
825                    "    \x1B[;32m\u{21B3} {:?} \x1B[;36m{:?}\x1B[;m",
826                    tok.1,
827                    tok.0
828                );
829                Some(Ok(tok))
830            }
831            Ok(None) => None,
832            Err(error) => {
833                self.failed = true;
834                Some(Err(error))
835            }
836        }
837    }
838}
839
840impl<'input, T: BorrowedInput<'input>> core::iter::FusedIterator for Scanner<'input, T> {}
841
842/// A convenience alias for scanner functions that may fail without returning a value.
843type ScanResult = Result<(), ScanError>;
844
845#[derive(Debug)]
846enum FlowScalarBuf {
847    /// Candidate for `Cow::Borrowed`.
848    ///
849    /// `start..end` is the committed verbatim range.
850    /// `pending_ws_start..pending_ws_end` is a run of blanks that were seen but not yet
851    /// committed (they must be dropped if followed by a line break).
852    Borrowed {
853        start: usize,
854        end: usize,
855        pending_ws_start: Option<usize>,
856        pending_ws_end: usize,
857    },
858    Owned(String),
859}
860
861impl FlowScalarBuf {
862    #[inline]
863    fn new_borrowed(start: usize) -> Self {
864        Self::Borrowed {
865            start,
866            end: start,
867            pending_ws_start: None,
868            pending_ws_end: start,
869        }
870    }
871
872    #[inline]
873    fn new_owned() -> Self {
874        Self::Owned(String::new())
875    }
876
877    #[inline]
878    fn as_owned_mut(&mut self) -> Option<&mut String> {
879        match self {
880            Self::Owned(s) => Some(s),
881            Self::Borrowed { .. } => None,
882        }
883    }
884
885    #[inline]
886    fn commit_pending_ws(&mut self) {
887        if let Self::Borrowed {
888            end,
889            pending_ws_start,
890            pending_ws_end,
891            ..
892        } = self
893        {
894            if pending_ws_start.is_some() {
895                *end = *pending_ws_end;
896                *pending_ws_start = None;
897            }
898        }
899    }
900
901    #[inline]
902    fn note_pending_ws(&mut self, ws_start: usize, ws_end: usize) {
903        if let Self::Borrowed {
904            pending_ws_start,
905            pending_ws_end,
906            ..
907        } = self
908        {
909            if pending_ws_start.is_none() {
910                *pending_ws_start = Some(ws_start);
911            }
912            *pending_ws_end = ws_end;
913        }
914    }
915
916    #[inline]
917    fn discard_pending_ws(&mut self) {
918        if let Self::Borrowed {
919            pending_ws_start,
920            pending_ws_end,
921            end,
922            ..
923        } = self
924        {
925            *pending_ws_start = None;
926            *pending_ws_end = *end;
927        }
928    }
929}
930
931impl<'input, T: BorrowedInput<'input>> Scanner<'input, T> {
932    #[inline]
933    fn promote_flow_scalar_buf_to_owned(
934        &self,
935        start_mark: &Marker,
936        buf: &mut FlowScalarBuf,
937    ) -> Result<(), ScanError> {
938        let FlowScalarBuf::Borrowed {
939            start,
940            end,
941            pending_ws_start: _,
942            pending_ws_end: _,
943        } = *buf
944        else {
945            return Ok(());
946        };
947
948        let slice = self.input.slice_bytes(start, end).ok_or_else(|| {
949            ScanError::from_kind(*start_mark, ErrorKind::InputOffsetsWithoutSlice)
950        })?;
951        *buf = FlowScalarBuf::Owned(slice.to_owned());
952        Ok(())
953    }
954    /// Try to borrow a slice from the underlying input.
955    ///
956    /// This method uses the [`BorrowedInput`] trait to safely obtain a slice with the `'input`
957    /// lifetime. For inputs that support zero-copy slicing (like `StrInput`), this returns
958    /// `Some(&'input str)`. For streaming inputs, this returns `None`.
959    #[inline]
960    fn try_borrow_slice(&self, start: usize, end: usize) -> Option<&'input str> {
961        self.input.slice_borrowed(start, end)
962    }
963
964    /// Scan a tag handle for a `%TAG` directive as a `Cow<str>`.
965    ///
966    /// For `StrInput`, this will borrow from the input when possible. For other inputs, or if
967    /// borrowing is not possible, it falls back to allocating.
968    fn scan_tag_handle_directive_cow(
969        &mut self,
970        mark: &Marker,
971    ) -> Result<Cow<'input, str>, ScanError> {
972        let Some(start) = self.input.byte_offset() else {
973            return Ok(Cow::Owned(self.scan_tag_handle(true, mark)?));
974        };
975
976        if self.input.look_ch() != '!' {
977            return Err(ScanError::from_kind(*mark, ErrorKind::ExpectedTagBang));
978        }
979
980        // Consume the leading '!'.
981        self.skip_non_blank();
982
983        // Consume ns-word-char (ASCII alphanumeric, '_' or '-') characters.
984        // This mirrors `StrInput::fetch_while_is_alpha` but avoids allocation.
985        self.input.lookahead(1);
986        while self.input.next_is_alpha() {
987            self.skip_non_blank();
988            self.input.lookahead(1);
989        }
990
991        // Optional trailing '!'.
992        if self.input.peek() == '!' {
993            self.skip_non_blank();
994        }
995
996        let Some(end) = self.input.byte_offset() else {
997            // Should be impossible if `byte_offset()` was `Some` above, but keep safe fallback.
998            return Ok(Cow::Owned(self.scan_tag_handle(true, mark)?));
999        };
1000
1001        let Some(slice) = self.try_borrow_slice(start, end) else {
1002            // Fall back to allocating if zero-copy borrow is not available.
1003            let slice = self
1004                .input
1005                .slice_bytes(start, end)
1006                .ok_or_else(|| ScanError::from_kind(*mark, ErrorKind::InputSlicingUnavailable))?;
1007            if !slice.ends_with('!') && slice != "!" {
1008                return Err(ScanError::from_kind(
1009                    *mark,
1010                    ErrorKind::ExpectedTagDirectiveBang,
1011                ));
1012            }
1013            return Ok(Cow::Owned(slice.to_owned()));
1014        };
1015
1016        if !slice.ends_with('!') && slice != "!" {
1017            return Err(ScanError::from_kind(
1018                *mark,
1019                ErrorKind::ExpectedTagDirectiveBang,
1020            ));
1021        }
1022
1023        Ok(Cow::Borrowed(slice))
1024    }
1025
1026    /// Scan a tag prefix for a `%TAG` directive as a `Cow<str>`.
1027    ///
1028    /// This borrows from `StrInput` only when no URI escape sequences are encountered. If a `%`
1029    /// escape is present, the prefix must be decoded and therefore allocated.
1030    fn scan_tag_prefix_directive_cow(
1031        &mut self,
1032        start_mark: &Marker,
1033    ) -> Result<Cow<'input, str>, ScanError> {
1034        let Some(start) = self.input.byte_offset() else {
1035            return Ok(Cow::Owned(self.scan_tag_prefix(start_mark)?));
1036        };
1037
1038        // The prefix must start with either '!' (local) or a valid global tag char.
1039        if self.input.look_ch() == '!' {
1040            self.skip_non_blank();
1041        } else if !is_tag_char(self.input.peek()) {
1042            return Err(ScanError::from_kind(
1043                *start_mark,
1044                ErrorKind::InvalidGlobalTagCharacter,
1045            ));
1046        } else if self.input.peek() == '%' {
1047            // Needs decoding. Fall back to allocating path below.
1048        } else {
1049            self.skip_non_blank();
1050        }
1051
1052        // Consume URI chars while we can stay in the borrowed path.
1053        while is_uri_char(self.input.look_ch()) {
1054            if self.input.peek() == '%' {
1055                break;
1056            }
1057            self.skip_non_blank();
1058        }
1059
1060        // If we encountered an escape sequence, we must decode, therefore allocate.
1061        if self.input.peek() == '%' {
1062            let current = self
1063                .input
1064                .byte_offset()
1065                .expect("byte_offset() must remain available once enabled");
1066            let mut out = if let Some(slice) = self.input.slice_bytes(start, current) {
1067                slice.to_owned()
1068            } else {
1069                String::new()
1070            };
1071
1072            while is_uri_char(self.input.look_ch()) {
1073                if self.input.peek() == '%' {
1074                    out.push(self.scan_uri_escapes(start_mark)?);
1075                } else {
1076                    out.push(self.input.peek());
1077                    self.skip_non_blank();
1078                }
1079            }
1080            return Ok(Cow::Owned(out));
1081        }
1082
1083        let Some(end) = self.input.byte_offset() else {
1084            return Ok(Cow::Owned(self.scan_tag_prefix(start_mark)?));
1085        };
1086
1087        let Some(slice) = self.try_borrow_slice(start, end) else {
1088            // Fall back to allocating if zero-copy borrow is not available.
1089            let slice = self.input.slice_bytes(start, end).ok_or_else(|| {
1090                ScanError::from_kind(*start_mark, ErrorKind::InputSlicingUnavailable)
1091            })?;
1092            return Ok(Cow::Owned(slice.to_owned()));
1093        };
1094
1095        Ok(Cow::Borrowed(slice))
1096    }
1097    /// Create a scanner over the given input source.
1098    pub fn new(input: T) -> Self {
1099        let initial_byte_offset = input.byte_offset();
1100        let comments_possible = input.may_contain_comments();
1101        Scanner {
1102            input,
1103            mark: Marker::new(0, 1, 0).with_byte_offset(initial_byte_offset),
1104            tokens: VecDeque::with_capacity(64),
1105            failed: false,
1106            deferred_error: None,
1107            comments_possible,
1108
1109            stream_start_produced: false,
1110            stream_end_produced: false,
1111            document_prefix_allowed: true,
1112            adjacent_value_allowed_at: 0,
1113            simple_key_allowed: true,
1114            simple_keys: smallvec::SmallVec::new(),
1115            indent: -1,
1116            indents: smallvec::SmallVec::new(),
1117            flow_level: 0,
1118            tokens_parsed: 0,
1119            token_available: false,
1120            leading_whitespace: true,
1121            flow_mapping_started: smallvec::SmallVec::new(),
1122            implicit_flow_mapping_states: smallvec::SmallVec::new(),
1123            flow_markers: smallvec::SmallVec::new(),
1124            interrupted_plain_by_comment: None,
1125            explicit_key_tab_check_pending: false,
1126
1127            buf_leading_break: String::with_capacity(128),
1128            buf_trailing_breaks: String::with_capacity(128),
1129            buf_whitespaces: String::with_capacity(128),
1130        }
1131    }
1132
1133    #[cold]
1134    fn scan_error(&self, kind: ErrorKind) -> ScanError {
1135        ScanError::from_kind(self.mark, kind)
1136    }
1137
1138    #[inline]
1139    fn ensure_current_char_is_printable(&self) -> ScanResult {
1140        let character = self.input.peek();
1141        if self.input.next_is_z() || is_printable(character) {
1142            Ok(())
1143        } else {
1144            Err(self.scan_error(ErrorKind::UnexpectedCharacter { character }))
1145        }
1146    }
1147
1148    #[cold]
1149    fn simple_key_expected(mark: Marker) -> ScanError {
1150        ScanError::from_kind(mark, ErrorKind::SimpleKeyExpected)
1151    }
1152
1153    #[cold]
1154    fn unclosed_bracket(mark: Marker, bracket: char) -> ScanError {
1155        ScanError::from_kind(mark, ErrorKind::UnclosedFlowCollection { open: bracket })
1156    }
1157
1158    /// Consume the next character. It is assumed the next character is a blank.
1159    #[inline]
1160    fn skip_blank(&mut self) {
1161        self.input.skip();
1162
1163        self.mark.offsets.chars += 1;
1164        self.mark.col += 1;
1165        self.mark.offsets.bytes = self.input.byte_offset();
1166    }
1167
1168    /// Consume the next character. It is assumed the next character is not a blank.
1169    #[inline]
1170    fn skip_non_blank(&mut self) {
1171        self.input.skip();
1172
1173        self.mark.offsets.chars += 1;
1174        self.mark.col += 1;
1175        self.mark.offsets.bytes = self.input.byte_offset();
1176        self.leading_whitespace = false;
1177    }
1178
1179    /// Consume a byte order mark from a document prefix.
1180    ///
1181    /// The source index advances, but the logical column remains unchanged so directives and
1182    /// document markers immediately following the BOM are still recognized as line-start tokens.
1183    #[inline]
1184    fn skip_bom(&mut self) {
1185        self.input.skip();
1186
1187        self.mark.offsets.chars += 1;
1188        self.mark.offsets.bytes = self.input.byte_offset();
1189    }
1190
1191    /// Consume one character that belongs to a comment.
1192    ///
1193    /// Unlike [`Self::skip_non_blank`], this deliberately does not change
1194    /// `leading_whitespace`. Comments are presentation content, so consuming one for either
1195    /// tokenization or skipping should only advance position bookkeeping.
1196    #[inline]
1197    fn skip_comment_char(&mut self) {
1198        self.input.skip();
1199
1200        self.mark.offsets.chars += 1;
1201        self.mark.col += 1;
1202        self.mark.offsets.bytes = self.input.byte_offset();
1203    }
1204
1205    /// Consume the next characters. It is assumed none of the next characters are blanks.
1206    #[inline]
1207    fn skip_n_non_blank(&mut self, count: usize) {
1208        for _ in 0..count {
1209            self.input.skip();
1210            self.mark.offsets.chars += 1;
1211            self.mark.col += 1;
1212        }
1213        self.mark.offsets.bytes = self.input.byte_offset();
1214        self.leading_whitespace = false;
1215    }
1216
1217    /// Consume the next character. It is assumed the next character is a newline.
1218    #[inline]
1219    fn skip_nl(&mut self) {
1220        self.input.skip();
1221
1222        self.mark.offsets.chars += 1;
1223        self.mark.col = 0;
1224        self.mark.line += 1;
1225        self.mark.offsets.bytes = self.input.byte_offset();
1226        self.leading_whitespace = true;
1227    }
1228
1229    /// Consume a line break (either CR, LF, or CRLF), if any. Do nothing if there is none.
1230    #[inline]
1231    fn skip_linebreak(&mut self) {
1232        if self.input.next_2_are('\r', '\n') {
1233            // While technically not a blank, this does not matter as `self.leading_whitespace`
1234            // will be reset by `skip_nl`.
1235            self.skip_blank();
1236            self.skip_nl();
1237        } else if self.input.next_is_break() {
1238            self.skip_nl();
1239        }
1240    }
1241
1242    #[cfg(test)]
1243    fn scan_comment_token(&mut self) -> Result<Token<'input>, ScanError> {
1244        Ok(self.scan_comment_queued_token()?.into_public())
1245    }
1246
1247    fn scan_comment_queued_token(&mut self) -> Result<QueuedToken<'input>, ScanError> {
1248        let start_mark = self.mark;
1249        debug_assert_eq!(self.input.peek(), '#');
1250        let placement = if self.leading_whitespace {
1251            Placement::Free
1252        } else {
1253            Placement::Right
1254        };
1255
1256        self.skip_comment_char();
1257
1258        let text = if let Some(start) = self.input.byte_offset() {
1259            // Stable byte offsets are available; slice the payload once at the end.
1260            let n = self.input.skip_while_non_breakz();
1261            self.mark.offsets.chars += n;
1262            self.mark.col += n;
1263            let byte_offset = self.input.byte_offset();
1264            self.mark.offsets.bytes = byte_offset;
1265            let end = byte_offset.expect("byte_offset must remain available once enabled");
1266
1267            if let Some(slice) = self.try_borrow_slice(start, end) {
1268                Cow::Borrowed(slice)
1269            } else if let Some(slice) = self.input.slice_bytes(start, end) {
1270                // Defensive fallback for third-party inputs that expose offsets but cannot borrow.
1271                Cow::Owned(slice.to_owned())
1272            } else {
1273                return Err(ScanError::from_kind(
1274                    start_mark,
1275                    ErrorKind::InputOffsetsWithoutSlice,
1276                ));
1277            }
1278        } else {
1279            // Streaming input without stable offsets; collect into an owned string.
1280            let mut owned = String::new();
1281            while {
1282                let character = self.input.look_ch();
1283                !is_breakz(character) && is_printable(character)
1284            } {
1285                owned.push(self.input.peek());
1286                self.skip_comment_char();
1287            }
1288            Cow::Owned(owned)
1289        };
1290
1291        self.ensure_current_char_is_printable()?;
1292
1293        let end_mark = self.mark;
1294        let span = Span::new(start_mark, end_mark);
1295        Ok(QueuedToken(
1296            span,
1297            QueuedTokenType::Comment(Comment { text, placement }),
1298        ))
1299    }
1300
1301    fn push_comment_token(&mut self) -> ScanResult {
1302        let token = self.scan_comment_queued_token()?;
1303        self.tokens.push_back(token);
1304        Ok(())
1305    }
1306
1307    fn skip_comment(&mut self) -> ScanResult {
1308        debug_assert_eq!(self.input.peek(), '#');
1309
1310        self.skip_comment_char();
1311        let n = self.input.skip_while_non_breakz();
1312        self.mark.offsets.chars += n;
1313        self.mark.col += n;
1314        self.mark.offsets.bytes = self.input.byte_offset();
1315        self.ensure_current_char_is_printable()
1316    }
1317
1318    /// Return whether the [`TokenType::StreamStart`] event has been emitted.
1319    #[inline]
1320    pub fn stream_started(&self) -> bool {
1321        self.stream_start_produced
1322    }
1323
1324    /// Return whether the [`TokenType::StreamEnd`] event has been emitted.
1325    #[inline]
1326    pub fn stream_ended(&self) -> bool {
1327        self.stream_end_produced
1328    }
1329
1330    /// Return the current position in the input stream.
1331    #[inline]
1332    pub fn mark(&self) -> Marker {
1333        self.mark
1334    }
1335
1336    /// Return whether this scanner may emit comment tokens.
1337    #[inline]
1338    pub(crate) fn comments_possible(&self) -> bool {
1339        self.comments_possible
1340    }
1341
1342    // Read and consume a line break (either `\r`, `\n` or `\r\n`).
1343    //
1344    // A `\n` is pushed into `s`.
1345    //
1346    // # Panics (in debug)
1347    // If the next characters do not correspond to a line break.
1348    #[inline]
1349    fn read_break(&mut self, s: &mut String) {
1350        self.skip_break();
1351        s.push('\n');
1352    }
1353
1354    // Read and consume a line break (either `\r`, `\n` or `\r\n`).
1355    //
1356    // # Panics (in debug)
1357    // If the next characters do not correspond to a line break.
1358    #[inline]
1359    fn skip_break(&mut self) {
1360        let c = self.input.peek();
1361        let nc = self.input.peek_nth(1);
1362        debug_assert!(is_break(c));
1363        if c == '\r' && nc == '\n' {
1364            self.skip_blank();
1365        }
1366        self.skip_nl();
1367    }
1368
1369    /// Insert a token at the given position.
1370    fn insert_token(&mut self, pos: usize, tok: Token<'input>) {
1371        let old_len = self.tokens.len();
1372        assert!(pos <= old_len);
1373        self.tokens.insert(pos, tok.into());
1374    }
1375
1376    fn simple_key_token_index(&self, sk: &SimpleKey, mark: Marker) -> Result<usize, ScanError> {
1377        let Some(index) = sk.token_number.checked_sub(self.tokens_parsed) else {
1378            return Err(ScanError::from_kind(mark, ErrorKind::InvalidSimpleKey));
1379        };
1380        if index > self.tokens.len() {
1381            return Err(ScanError::from_kind(mark, ErrorKind::InvalidSimpleKey));
1382        }
1383        Ok(index)
1384    }
1385
1386    #[inline]
1387    fn allow_simple_key(&mut self) {
1388        self.simple_key_allowed = true;
1389    }
1390
1391    #[inline]
1392    fn disallow_simple_key(&mut self) {
1393        self.simple_key_allowed = false;
1394    }
1395
1396    /// Scan enough input to append one next token to the internal token queue.
1397    ///
1398    /// # Errors
1399    /// Returns `ScanError` when the scanner does not find the next expected token.
1400    fn fetch_next_token(&mut self) -> ScanResult {
1401        let result = self.fetch_next_token_impl();
1402        if let Some(kind) = self.input.take_source_error() {
1403            return Err(ScanError::from_kind(self.mark, kind));
1404        }
1405        result
1406    }
1407
1408    fn fetch_next_token_impl(&mut self) -> ScanResult {
1409        self.input.lookahead(1);
1410
1411        if !self.stream_start_produced {
1412            self.fetch_stream_start();
1413            return Ok(());
1414        }
1415        if self.skip_to_next_token(true)? {
1416            return Ok(());
1417        }
1418
1419        debug_print!(
1420            "  \x1B[38;5;244m\u{2192} fetch_next_token after whitespace {:?} {:?}\x1B[m",
1421            self.mark,
1422            self.input.peek()
1423        );
1424
1425        self.stale_simple_keys()?;
1426
1427        let mark = self.mark;
1428        self.unroll_indent(mark.col as isize);
1429
1430        self.input.lookahead(4);
1431
1432        if self.input.next_is_z() {
1433            self.fetch_stream_end()?;
1434            return Ok(());
1435        }
1436
1437        self.ensure_current_char_is_printable()?;
1438
1439        if self.mark.col == 0 {
1440            if self.input.next_char_is('%') {
1441                return self.fetch_directive();
1442            } else if self.input.next_is_document_start() {
1443                return self.fetch_document_indicator(TokenType::DocumentStart);
1444            } else if self.input.next_is_document_end() {
1445                self.fetch_document_indicator(TokenType::DocumentEnd)?;
1446                self.skip_ws_to_eol(SkipTabs::Yes)?;
1447                if !self.input.next_is_breakz() {
1448                    return Err(self.scan_error(ErrorKind::InvalidDocumentEnd));
1449                }
1450                return Ok(());
1451            }
1452        }
1453
1454        if self.document_prefix_allowed {
1455            self.document_prefix_allowed = false;
1456        }
1457
1458        if (self.mark.col as isize) < self.indent {
1459            self.input.lookahead(1);
1460            let c = self.input.peek();
1461            if self.flow_level == 0 || !matches!(c, ']' | '}' | ',') {
1462                return Err(self.scan_error(ErrorKind::InvalidIndentation));
1463            }
1464        }
1465
1466        let c = self.input.peek();
1467        let nc = self.input.peek_nth(1);
1468        match c {
1469            '[' => self.fetch_flow_collection_start(TokenType::FlowSequenceStart),
1470            '{' => self.fetch_flow_collection_start(TokenType::FlowMappingStart),
1471            ']' => self.fetch_flow_collection_end(TokenType::FlowSequenceEnd, '[', ']'),
1472            '}' => self.fetch_flow_collection_end(TokenType::FlowMappingEnd, '{', '}'),
1473            ',' => self.fetch_flow_entry(),
1474            '-' if is_blank_or_breakz(nc) => self.fetch_block_entry(),
1475            '?' if is_blank_or_breakz(nc) => self.fetch_key(),
1476            ':' if is_blank_or_breakz(nc) => self.fetch_value(),
1477            ':' if self.flow_level > 0
1478                && (is_flow(nc) || self.mark.index() == self.adjacent_value_allowed_at) =>
1479            {
1480                self.fetch_flow_value()
1481            }
1482            // Is it an alias?
1483            '*' => self.fetch_anchor(true),
1484            // Is it an anchor?
1485            '&' => self.fetch_anchor(false),
1486            '!' => self.fetch_tag(),
1487            // Is it a literal scalar?
1488            '|' if self.flow_level == 0 => self.fetch_block_scalar(true),
1489            // Is it a folded scalar?
1490            '>' if self.flow_level == 0 => self.fetch_block_scalar(false),
1491            '\'' => self.fetch_flow_scalar(true),
1492            '"' => self.fetch_flow_scalar(false),
1493            // plain scalar
1494            '-' if !is_blank_or_breakz(nc) => self.fetch_plain_scalar(),
1495            ':' | '?' if !is_blank_or_breakz(nc) && self.flow_level == 0 => {
1496                self.fetch_plain_scalar()
1497            }
1498            c if is_bom(c) => Err(self.scan_error(ErrorKind::BomInsideDocument)),
1499            '%' | '@' | '`' => {
1500                Err(self.scan_error(ErrorKind::UnexpectedCharacter { character: c }))
1501            }
1502            _ => self.fetch_plain_scalar(),
1503        }
1504    }
1505
1506    /// Return the next compact queued token, scanning more input when needed.
1507    ///
1508    /// # Errors
1509    /// Returns `ScanError` when scanning fails to find an expected next token.
1510    pub(crate) fn next_queued_token(&mut self) -> Result<Option<QueuedToken<'input>>, ScanError> {
1511        if self.deferred_error.is_some() {
1512            if !matches!(
1513                self.tokens.front().map(|token| &token.1),
1514                Some(QueuedTokenType::Comment(_))
1515            ) {
1516                if let Some(error) = self.deferred_error.take() {
1517                    return error.into_result();
1518                }
1519            }
1520            self.token_available = true;
1521        }
1522
1523        if self.stream_end_produced {
1524            return Ok(None);
1525        }
1526
1527        if !self.token_available {
1528            if let Err(error) = self.fetch_more_tokens() {
1529                if matches!(
1530                    self.tokens.front().map(|token| &token.1),
1531                    Some(QueuedTokenType::Comment(_))
1532                ) {
1533                    self.deferred_error = Some(error);
1534                } else {
1535                    return Err(error);
1536                }
1537            }
1538        }
1539        let Some(t) = self.tokens.pop_front() else {
1540            unreachable!("fetch_more_tokens succeeded without producing a token")
1541        };
1542        self.token_available = false;
1543        self.tokens_parsed += 1;
1544
1545        let is_stream_end = matches!(t.1, QueuedTokenType::StreamEnd);
1546        if is_stream_end {
1547            self.stream_end_produced = true;
1548        }
1549        Ok(Some(t))
1550    }
1551
1552    /// Return the next queued token, scanning more input when needed.
1553    ///
1554    /// # Errors
1555    /// Returns `ScanError` when scanning fails to find an expected next token.
1556    fn next_token(&mut self) -> Result<Option<Token<'input>>, ScanError> {
1557        Ok(self.next_queued_token()?.map(QueuedToken::into_public))
1558    }
1559
1560    /// Scan more input until a token is ready to be returned.
1561    ///
1562    /// # Errors
1563    /// Returns `ScanError` when scanning fails.
1564    fn fetch_more_tokens(&mut self) -> ScanResult {
1565        let mut need_more;
1566        loop {
1567            if self.tokens.is_empty() {
1568                need_more = true;
1569            } else {
1570                need_more = false;
1571                // Stale potential keys that we know won't be keys.
1572                self.stale_simple_keys()?;
1573                if !matches!(
1574                    self.tokens.front().map(|token| &token.1),
1575                    Some(QueuedTokenType::Comment(_))
1576                ) {
1577                    // If our next token to be emitted may be a key, fetch more context.
1578                    for sk in &self.simple_keys {
1579                        if sk.possible && sk.token_number == self.tokens_parsed {
1580                            need_more = true;
1581                            break;
1582                        }
1583                    }
1584                }
1585            }
1586
1587            // Stop fetching immediately after document end/start markers
1588            // to allow the parser to emit the event before reading more content.
1589            if let Some(token) = self.tokens.back() {
1590                if matches!(
1591                    token.1,
1592                    QueuedTokenType::DocumentEnd | QueuedTokenType::DocumentStart
1593                ) {
1594                    break;
1595                }
1596            }
1597
1598            if !need_more {
1599                break;
1600            }
1601            self.fetch_next_token()?;
1602        }
1603        self.token_available = true;
1604
1605        Ok(())
1606    }
1607
1608    /// Mark simple keys that can no longer be keys as such.
1609    ///
1610    /// This function sets `possible` to `false` to each key that, now we have more context, we
1611    /// know will not be keys.
1612    ///
1613    /// # Errors
1614    /// This function returns an error if one of the keys becoming impossible was required to be a
1615    /// key.
1616    fn stale_simple_keys(&mut self) -> ScanResult {
1617        for sk in &mut self.simple_keys {
1618            let is_line_stale = self.flow_level == 0 && sk.mark.line < self.mark.line;
1619            // The length cap applies in flow contexts too; otherwise token buffering can grow
1620            // without bound while the scanner waits to see whether a later ':' resolves the key.
1621            let is_length_stale =
1622                self.mark.index().saturating_sub(sk.mark.index()) > SIMPLE_KEY_MAX_LOOKAHEAD;
1623
1624            if sk.possible && (is_line_stale || is_length_stale) {
1625                if sk.required {
1626                    return Err(Self::simple_key_expected(sk.mark));
1627                }
1628                sk.possible = false;
1629            }
1630        }
1631        Ok(())
1632    }
1633
1634    /// Skip over whitespace (`\t`, ` `, `\n`, `\r`) until the next non-comment token.
1635    ///
1636    /// Comments encountered while skipping are queued as [`TokenType::Comment`] tokens so the
1637    /// parser can emit them as presentation events. If `stop_after_comment` is true, the function
1638    /// returns after queuing one comment so callers can emit it before scanning later comments.
1639    ///
1640    /// # Errors
1641    /// This function returns an error if a tab is encountered where there should not be
1642    /// one.
1643    fn skip_to_next_token(&mut self, stop_after_comment: bool) -> Result<bool, ScanError> {
1644        // Hot-path helper: consume a single logical line break and apply simple-key rules.
1645        // (Kept local to ensure the compiler can inline it easily.)
1646        let consume_linebreak = |this: &mut Self| {
1647            this.input.lookahead(2);
1648            this.skip_linebreak();
1649            if this.flow_level == 0 {
1650                this.allow_simple_key();
1651            }
1652        };
1653
1654        loop {
1655            let ch = self.input.look_ch();
1656            if self.explicit_key_tab_check_pending {
1657                match ch {
1658                    '\t' => {
1659                        return Err(self.scan_error(ErrorKind::TabNotAllowed));
1660                    }
1661                    ' ' | '\n' | '\r' | '#' => {}
1662                    _ => self.explicit_key_tab_check_pending = false,
1663                }
1664            }
1665
1666            match ch {
1667                // Tabs may not be used as indentation (block context only).
1668                '\t' => {
1669                    if self.is_within_block()
1670                        && self.leading_whitespace
1671                        && (self.mark.col as isize) < self.indent
1672                    {
1673                        self.skip_ws_to_eol(SkipTabs::Yes)?;
1674
1675                        // If we have content on that line with a tab, return an error.
1676                        if !self.input.next_is_breakz() {
1677                            return Err(self.scan_error(ErrorKind::TabInBlockIndentation));
1678                        }
1679
1680                        // Micro-opt: if we stopped on a line break, consume it now (avoids another loop trip).
1681                        if matches!(self.input.look_ch(), '\n' | '\r') {
1682                            consume_linebreak(self);
1683                        }
1684                    } else {
1685                        // Non-indentation tab behaves like blank.
1686                        self.skip_blank();
1687                    }
1688                }
1689
1690                ' ' => self.skip_blank(),
1691
1692                '\n' | '\r' => consume_linebreak(self),
1693
1694                c if is_bom(c)
1695                    && self.document_prefix_allowed
1696                    && self.flow_level == 0
1697                    && self.mark.col == 0 =>
1698                {
1699                    self.skip_bom();
1700                }
1701
1702                '#' => {
1703                    self.push_comment_token()?;
1704
1705                    // Micro-opt: comment-only lines are common; consume the following line break here.
1706                    if matches!(self.input.look_ch(), '\n' | '\r') {
1707                        consume_linebreak(self);
1708                    }
1709                    if stop_after_comment {
1710                        return Ok(true);
1711                    }
1712                }
1713
1714                _ => break,
1715            }
1716        }
1717
1718        // If a plain scalar was interrupted by a comment, and the next line could
1719        // continue the scalar in block context, this is invalid.
1720        if let Some(err_mark) = self.interrupted_plain_by_comment.take() {
1721            // BS4K should only trigger when the continuation would start on the immediate next
1722            // line (no intervening empty/comment-only lines). A blank line resets the folding
1723            // opportunity and thus should not error.
1724            let is_immediate_next_line = self.mark.line == err_mark.line + 1;
1725
1726            // Optimization: do the cheap checks first; only then request extra lookahead / do deeper checks.
1727            if self.flow_level == 0
1728                && is_immediate_next_line
1729                && (self.mark.col as isize) > self.indent
1730            {
1731                // Ensure enough lookahead for:
1732                // - the checks below (peek/peek_nth)
1733                // - document indicator detection which needs 4 chars.
1734                self.input.lookahead(4);
1735
1736                if !self.input.next_is_z()
1737                    && !self.input.next_is_document_indicator()
1738                    && self.input.next_can_be_plain_scalar(false)
1739                {
1740                    return Err(ScanError::from_kind(
1741                        err_mark,
1742                        ErrorKind::CommentInterceptedScalar,
1743                    ));
1744                }
1745            }
1746        }
1747
1748        Ok(false)
1749    }
1750
1751    /// Skip over YAML whitespace (` `, `\n`, `\r`).
1752    ///
1753    /// If `stop_after_comment` is true, the function returns after queuing one comment so callers
1754    /// can emit it before scanning later comments.
1755    ///
1756    /// # Errors
1757    /// This function returns an error if no whitespace was found.
1758    fn skip_yaml_whitespace(&mut self, stop_after_comment: bool) -> Result<bool, ScanError> {
1759        let mut need_whitespace = true;
1760        loop {
1761            match self.input.look_ch() {
1762                ' ' => {
1763                    self.skip_blank();
1764
1765                    need_whitespace = false;
1766                }
1767                '\n' | '\r' => {
1768                    self.input.lookahead(2);
1769                    self.skip_linebreak();
1770                    if self.flow_level == 0 {
1771                        self.allow_simple_key();
1772                    }
1773                    need_whitespace = false;
1774                }
1775                '#' => {
1776                    if need_whitespace {
1777                        self.skip_comment()?;
1778                    } else {
1779                        self.push_comment_token()?;
1780                        if stop_after_comment {
1781                            return Ok(true);
1782                        }
1783                    }
1784                }
1785                _ => break,
1786            }
1787        }
1788
1789        if need_whitespace {
1790            Err(self.scan_error(ErrorKind::ExpectedWhitespace))
1791        } else {
1792            Ok(false)
1793        }
1794    }
1795
1796    fn skip_ws_to_eol(&mut self, skip_tabs: SkipTabs) -> Result<SkipTabs, ScanError> {
1797        debug_assert!(!matches!(skip_tabs, SkipTabs::Result(..)));
1798
1799        if !self.comments_possible {
1800            let (chars_consumed, result) = self.input.skip_ws_to_eol(skip_tabs);
1801            self.mark.col += chars_consumed;
1802            self.mark.offsets.chars += chars_consumed;
1803            self.mark.offsets.bytes = self.input.byte_offset();
1804            return result.map_err(|kind| self.scan_error(kind));
1805        }
1806
1807        let (chars_consumed, whitespace) = self.input.skip_ws_to_eol_blanks(skip_tabs);
1808        self.mark.col += chars_consumed;
1809        self.mark.offsets.chars += chars_consumed;
1810        self.mark.offsets.bytes = self.input.byte_offset();
1811
1812        if self.input.look_ch() != '#' {
1813            return Ok(whitespace);
1814        }
1815
1816        if !whitespace.found_tabs() && !whitespace.has_valid_yaml_ws() {
1817            return Err(self.scan_error(ErrorKind::CommentNotSeparated));
1818        }
1819
1820        self.push_comment_token()?;
1821        Ok(whitespace)
1822    }
1823
1824    fn fetch_stream_start(&mut self) {
1825        let mark = self.mark;
1826        self.indent = -1;
1827        self.stream_start_produced = true;
1828        self.allow_simple_key();
1829        self.tokens
1830            .push_back(Token(Span::empty(mark), TokenType::StreamStart).into());
1831        self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0)));
1832    }
1833
1834    fn fetch_stream_end(&mut self) -> ScanResult {
1835        // force new line
1836        if self.mark.col != 0 {
1837            self.mark.col = 0;
1838            self.mark.line += 1;
1839        }
1840
1841        if let Some((mark, bracket)) = self.flow_markers.pop() {
1842            return Err(Self::unclosed_bracket(mark, bracket));
1843        }
1844
1845        // If the stream ended, we won't have more context. We can stall all the simple keys we
1846        // had. If one was required, however, that was an error and we must propagate it.
1847        for sk in &mut self.simple_keys {
1848            if sk.required && sk.possible {
1849                return Err(Self::simple_key_expected(sk.mark));
1850            }
1851            sk.possible = false;
1852        }
1853
1854        self.unroll_indent(-1);
1855        self.remove_simple_key()?;
1856        self.disallow_simple_key();
1857
1858        self.tokens
1859            .push_back(Token(Span::empty(self.mark), TokenType::StreamEnd).into());
1860        Ok(())
1861    }
1862
1863    fn fetch_directive(&mut self) -> ScanResult {
1864        self.unroll_indent(-1);
1865        self.remove_simple_key()?;
1866
1867        self.disallow_simple_key();
1868
1869        let token_index = self.tokens.len();
1870        let tok = self.scan_directive()?;
1871        self.insert_token(token_index, tok);
1872
1873        Ok(())
1874    }
1875
1876    fn scan_directive(&mut self) -> Result<Token<'input>, ScanError> {
1877        let start_mark = self.mark;
1878        self.skip_non_blank();
1879
1880        let name = self.scan_directive_name()?;
1881        let tok = match name.as_ref() {
1882            "YAML" => self.scan_version_directive_value(&start_mark)?,
1883            "TAG" => self.scan_tag_directive_value(&start_mark)?,
1884            _ => {
1885                let mut params = Vec::new();
1886                while self.input.next_is_blank() {
1887                    let n_blanks = self.input.skip_while_blank();
1888                    self.mark.offsets.chars += n_blanks;
1889                    self.mark.col += n_blanks;
1890                    self.mark.offsets.bytes = self.input.byte_offset();
1891
1892                    if !is_blank_or_breakz(self.input.peek()) {
1893                        let mut param = String::new();
1894                        let n_chars = self.input.fetch_while_is_yaml_non_space(&mut param);
1895                        self.mark.offsets.chars += n_chars;
1896                        self.mark.col += n_chars;
1897                        self.mark.offsets.bytes = self.input.byte_offset();
1898                        params.push(param);
1899                    }
1900                }
1901
1902                Token(
1903                    Span::new(start_mark, self.mark),
1904                    TokenType::ReservedDirective(name, params),
1905                )
1906            }
1907        };
1908
1909        self.skip_ws_to_eol(SkipTabs::Yes)?;
1910
1911        if self.input.next_is_breakz() {
1912            self.input.lookahead(2);
1913            self.skip_linebreak();
1914            Ok(tok)
1915        } else {
1916            Err(ScanError::from_kind(
1917                start_mark,
1918                ErrorKind::InvalidDirectiveTerminator,
1919            ))
1920        }
1921    }
1922
1923    fn scan_version_directive_value(&mut self, mark: &Marker) -> Result<Token<'input>, ScanError> {
1924        let n_blanks = self.input.skip_while_blank();
1925        self.mark.offsets.chars += n_blanks;
1926        self.mark.col += n_blanks;
1927        self.mark.offsets.bytes = self.input.byte_offset();
1928
1929        let major = self.scan_version_directive_number(mark)?;
1930
1931        if self.input.peek() != '.' {
1932            return Err(ScanError::from_kind(
1933                *mark,
1934                ErrorKind::MissingYamlVersionSeparator,
1935            ));
1936        }
1937        self.skip_non_blank();
1938
1939        let minor = self.scan_version_directive_number(mark)?;
1940
1941        Ok(Token(
1942            Span::new(*mark, self.mark),
1943            TokenType::VersionDirective(major, minor),
1944        ))
1945    }
1946
1947    fn scan_directive_name(&mut self) -> Result<String, ScanError> {
1948        let start_mark = self.mark;
1949        let mut string = String::new();
1950
1951        let n_chars = self.input.fetch_while_is_yaml_non_space(&mut string);
1952        self.mark.offsets.chars += n_chars;
1953        self.mark.col += n_chars;
1954        self.mark.offsets.bytes = self.input.byte_offset();
1955
1956        if string.is_empty() {
1957            return Err(ScanError::from_kind(
1958                start_mark,
1959                ErrorKind::MissingDirectiveName,
1960            ));
1961        }
1962
1963        if !is_blank_or_breakz(self.input.peek()) {
1964            return Err(ScanError::from_kind(
1965                start_mark,
1966                ErrorKind::InvalidDirectiveName,
1967            ));
1968        }
1969
1970        Ok(string)
1971    }
1972
1973    fn scan_version_directive_number(&mut self, mark: &Marker) -> Result<u32, ScanError> {
1974        let mut val = 0u32;
1975        let mut length = 0usize;
1976        while let Some(digit) = self.input.look_ch().to_digit(10) {
1977            if length + 1 > 9 {
1978                return Err(ScanError::from_kind(*mark, ErrorKind::YamlVersionTooLong));
1979            }
1980            length += 1;
1981            val = val * 10 + digit;
1982            self.skip_non_blank();
1983        }
1984
1985        if length == 0 {
1986            return Err(ScanError::from_kind(*mark, ErrorKind::MissingYamlVersion));
1987        }
1988
1989        Ok(val)
1990    }
1991
1992    fn scan_tag_directive_value(&mut self, mark: &Marker) -> Result<Token<'input>, ScanError> {
1993        let n_blanks = self.input.skip_while_blank();
1994        self.mark.offsets.chars += n_blanks;
1995        self.mark.col += n_blanks;
1996        self.mark.offsets.bytes = self.input.byte_offset();
1997
1998        let handle = self.scan_tag_handle_directive_cow(mark)?;
1999
2000        let n_blanks = self.input.skip_while_blank();
2001        self.mark.offsets.chars += n_blanks;
2002        self.mark.col += n_blanks;
2003        self.mark.offsets.bytes = self.input.byte_offset();
2004
2005        let prefix = self.scan_tag_prefix_directive_cow(mark)?;
2006
2007        self.input.lookahead(1);
2008
2009        if self.input.next_is_blank_or_breakz() {
2010            Ok(Token(
2011                Span::new(*mark, self.mark),
2012                TokenType::TagDirective(handle, prefix),
2013            ))
2014        } else {
2015            Err(ScanError::from_kind(
2016                *mark,
2017                ErrorKind::InvalidTagDirectiveTerminator,
2018            ))
2019        }
2020    }
2021
2022    fn fetch_tag(&mut self) -> ScanResult {
2023        self.save_simple_key();
2024        self.disallow_simple_key();
2025
2026        let tok = self.scan_tag()?;
2027        self.tokens.push_back(tok.into());
2028        Ok(())
2029    }
2030
2031    fn scan_tag(&mut self) -> Result<Token<'input>, ScanError> {
2032        let start_mark = self.mark;
2033
2034        // Check if the tag is in the canonical form (verbatim).
2035        self.input.lookahead(2);
2036
2037        // If byte_offset is not available, use the original owned-only path.
2038        if self.input.byte_offset().is_none() {
2039            return self.scan_tag_owned(&start_mark);
2040        }
2041
2042        let (handle, suffix): (Cow<'input, str>, Cow<'input, str>) = if self
2043            .input
2044            .nth_char_is(1, '<')
2045        {
2046            // Verbatim tags always need owned strings (URI escapes).
2047            let suffix = self.scan_verbatim_tag(&start_mark)?;
2048            (Cow::Owned(String::new()), Cow::Owned(suffix))
2049        } else {
2050            // The tag has either the '!suffix' or the '!handle!suffix'
2051            let handle = self.scan_tag_handle_cow(&start_mark)?;
2052            // Check if it is, indeed, handle.
2053            if handle.len() >= 2 && handle.starts_with('!') && handle.ends_with('!') {
2054                // A tag handle starting with "!!" is a secondary tag handle.
2055                let suffix = self.scan_tag_shorthand_suffix_cow(&start_mark, true)?;
2056                (handle, suffix)
2057            } else {
2058                // Not a real handle, it's part of the suffix.
2059                // E.g., "!foo" -> handle="!", suffix="foo"
2060                // The "handle" we scanned is actually "!" + suffix_part1.
2061                // We need to also scan any remaining suffix characters.
2062                let remaining_suffix = self.scan_tag_shorthand_suffix_cow(&start_mark, false)?;
2063
2064                let suffix = self.combine_local_tag_suffix(&start_mark, handle, remaining_suffix);
2065
2066                // A special case: the '!' tag.  Set the handle to '' and the
2067                // suffix to '!'.
2068                if suffix.is_empty() {
2069                    (Cow::Borrowed(""), Cow::Borrowed("!"))
2070                } else {
2071                    (Cow::Borrowed("!"), suffix)
2072                }
2073            }
2074        };
2075
2076        if is_blank_or_breakz(self.input.look_ch())
2077            || (self.flow_level > 0 && matches!(self.input.peek(), ',' | ']' | '}'))
2078        {
2079            // YAML example 7.2 allows a tag to annotate an empty scalar when a separator or flow
2080            // delimiter follows.
2081            Ok(Token(
2082                Span::new(start_mark, self.mark),
2083                TokenType::Tag(handle, suffix),
2084            ))
2085        } else {
2086            Err(ScanError::from_kind(
2087                start_mark,
2088                ErrorKind::InvalidTagTerminator,
2089            ))
2090        }
2091    }
2092
2093    fn combine_local_tag_suffix(
2094        &self,
2095        start_mark: &Marker,
2096        handle: Cow<'input, str>,
2097        remaining: Cow<'input, str>,
2098    ) -> Cow<'input, str> {
2099        if handle.len() == 1 {
2100            return remaining;
2101        }
2102
2103        match (handle, remaining) {
2104            (Cow::Borrowed(handle), Cow::Borrowed(remaining)) => {
2105                if remaining.is_empty() {
2106                    return Cow::Borrowed(&handle[1..]);
2107                }
2108
2109                let borrowed = start_mark
2110                    .byte_offset()
2111                    .and_then(|start| start.checked_add(1))
2112                    .zip(self.input.byte_offset())
2113                    .and_then(|(start, end)| self.try_borrow_slice(start, end));
2114                borrowed.map_or_else(
2115                    || {
2116                        let mut combined =
2117                            String::with_capacity(handle.len() - 1 + remaining.len());
2118                        combined.push_str(&handle[1..]);
2119                        combined.push_str(remaining);
2120                        Cow::Owned(combined)
2121                    },
2122                    Cow::Borrowed,
2123                )
2124            }
2125            (Cow::Borrowed(handle), Cow::Owned(mut remaining)) => {
2126                remaining.reserve(handle.len() - 1);
2127                remaining.insert_str(0, &handle[1..]);
2128                Cow::Owned(remaining)
2129            }
2130            (Cow::Owned(mut handle), remaining) => {
2131                handle.remove(0);
2132                handle.push_str(&remaining);
2133                Cow::Owned(handle)
2134            }
2135        }
2136    }
2137
2138    /// Original owned-only tag scanning path for inputs without `byte_offset` support.
2139    fn scan_tag_owned(&mut self, start_mark: &Marker) -> Result<Token<'input>, ScanError> {
2140        let mut handle = String::new();
2141        let mut suffix;
2142
2143        if self.input.nth_char_is(1, '<') {
2144            suffix = self.scan_verbatim_tag(start_mark)?;
2145        } else {
2146            // The tag has either the '!suffix' or the '!handle!suffix'
2147            handle = self.scan_tag_handle(false, start_mark)?;
2148            // Check if it is, indeed, handle.
2149            if handle.len() >= 2 && handle.starts_with('!') && handle.ends_with('!') {
2150                // A tag handle starting with "!!" is a secondary tag handle.
2151                let is_secondary_handle = handle == "!!";
2152                suffix =
2153                    self.scan_tag_shorthand_suffix(false, is_secondary_handle, "", start_mark)?;
2154            } else {
2155                suffix = self.scan_tag_shorthand_suffix(false, false, &handle, start_mark)?;
2156                "!".clone_into(&mut handle);
2157                // A special case: the '!' tag.  Set the handle to '' and the
2158                // suffix to '!'.
2159                if suffix.is_empty() {
2160                    handle.clear();
2161                    "!".clone_into(&mut suffix);
2162                }
2163            }
2164        }
2165
2166        if is_blank_or_breakz(self.input.look_ch())
2167            || (self.flow_level > 0 && matches!(self.input.peek(), ',' | ']' | '}'))
2168        {
2169            // YAML example 7.2 allows a tag to annotate an empty scalar when a separator or flow
2170            // delimiter follows.
2171            Ok(Token(
2172                Span::new(*start_mark, self.mark),
2173                TokenType::Tag(handle.into(), suffix.into()),
2174            ))
2175        } else {
2176            Err(ScanError::from_kind(
2177                *start_mark,
2178                ErrorKind::InvalidTagTerminator,
2179            ))
2180        }
2181    }
2182
2183    /// Scan a tag handle as a `Cow<str>`, borrowing when possible.
2184    ///
2185    /// Tag handles are of the form `!`, `!!`, or `!name!` where name is ASCII alphanumeric.
2186    /// Since they contain no escape sequences, they can always be borrowed from `StrInput`.
2187    fn scan_tag_handle_cow(&mut self, mark: &Marker) -> Result<Cow<'input, str>, ScanError> {
2188        let Some(start) = self.input.byte_offset() else {
2189            return Ok(Cow::Owned(self.scan_tag_handle(false, mark)?));
2190        };
2191
2192        if self.input.look_ch() != '!' {
2193            return Err(ScanError::from_kind(*mark, ErrorKind::ExpectedTagBang));
2194        }
2195
2196        // Consume the leading '!'.
2197        self.skip_non_blank();
2198
2199        // Consume ns-word-char (ASCII alphanumeric, '_' or '-') characters.
2200        self.input.lookahead(1);
2201        while self.input.next_is_alpha() {
2202            self.skip_non_blank();
2203            self.input.lookahead(1);
2204        }
2205
2206        // Optional trailing '!'.
2207        if self.input.peek() == '!' {
2208            self.skip_non_blank();
2209        }
2210
2211        let Some(end) = self.input.byte_offset() else {
2212            return Ok(Cow::Owned(self.scan_tag_handle(false, mark)?));
2213        };
2214
2215        if let Some(slice) = self.try_borrow_slice(start, end) {
2216            Ok(Cow::Borrowed(slice))
2217        } else {
2218            let slice = self
2219                .input
2220                .slice_bytes(start, end)
2221                .ok_or_else(|| ScanError::from_kind(*mark, ErrorKind::InputSlicingUnavailable))?;
2222            Ok(Cow::Owned(slice.to_owned()))
2223        }
2224    }
2225
2226    /// Scan a tag shorthand suffix as a `Cow<str>`, borrowing when possible.
2227    ///
2228    /// The suffix can be borrowed only if no `%` URI escape sequences are present.
2229    fn scan_tag_shorthand_suffix_cow(
2230        &mut self,
2231        mark: &Marker,
2232        require_non_empty: bool,
2233    ) -> Result<Cow<'input, str>, ScanError> {
2234        let Some(start) = self.input.byte_offset() else {
2235            return Ok(Cow::Owned(
2236                self.scan_tag_shorthand_suffix(false, false, "", mark)?,
2237            ));
2238        };
2239
2240        // Scan tag characters, checking for URI escapes.
2241        while is_tag_char(self.input.look_ch()) {
2242            if self.input.peek() == '%' {
2243                // URI escape found - must decode, so fall back to owned path.
2244                let current = self
2245                    .input
2246                    .byte_offset()
2247                    .expect("byte_offset() must remain available once enabled");
2248                let mut out = if let Some(slice) = self.input.slice_bytes(start, current) {
2249                    slice.to_owned()
2250                } else {
2251                    String::new()
2252                };
2253
2254                // Continue scanning with owned buffer.
2255                while is_tag_char(self.input.look_ch()) {
2256                    if self.input.peek() == '%' {
2257                        out.push(self.scan_uri_escapes(mark)?);
2258                    } else {
2259                        out.push(self.input.peek());
2260                        self.skip_non_blank();
2261                    }
2262                }
2263                return Ok(Cow::Owned(out));
2264            }
2265            self.skip_non_blank();
2266        }
2267
2268        let Some(end) = self.input.byte_offset() else {
2269            return Ok(Cow::Owned(
2270                self.scan_tag_shorthand_suffix(false, false, "", mark)?,
2271            ));
2272        };
2273
2274        if require_non_empty && start == end {
2275            return Err(ScanError::from_kind(*mark, ErrorKind::MissingTagUri));
2276        }
2277
2278        if let Some(slice) = self.try_borrow_slice(start, end) {
2279            Ok(Cow::Borrowed(slice))
2280        } else {
2281            let slice = self
2282                .input
2283                .slice_bytes(start, end)
2284                .ok_or_else(|| ScanError::from_kind(*mark, ErrorKind::InputSlicingUnavailable))?;
2285            Ok(Cow::Owned(slice.to_owned()))
2286        }
2287    }
2288
2289    fn scan_tag_handle(&mut self, directive: bool, mark: &Marker) -> Result<String, ScanError> {
2290        let mut string = String::new();
2291        if self.input.look_ch() != '!' {
2292            return Err(ScanError::from_kind(*mark, ErrorKind::ExpectedTagBang));
2293        }
2294
2295        string.push(self.input.peek());
2296        self.skip_non_blank();
2297
2298        let n_chars = self.input.fetch_while_is_alpha(&mut string);
2299        self.mark.offsets.chars += n_chars;
2300        self.mark.col += n_chars;
2301        self.mark.offsets.bytes = self.input.byte_offset();
2302
2303        // Check if the trailing character is '!' and copy it.
2304        if self.input.peek() == '!' {
2305            string.push(self.input.peek());
2306            self.skip_non_blank();
2307        } else if directive && string != "!" {
2308            // It's either the '!' tag or not really a tag handle.  If it's a %TAG
2309            // directive, it's an error.  If it's a tag token, it must be a part of
2310            // URI.
2311            return Err(ScanError::from_kind(
2312                *mark,
2313                ErrorKind::ExpectedTagDirectiveBang,
2314            ));
2315        }
2316        Ok(string)
2317    }
2318
2319    /// Scan for a tag prefix (6.8.2.2).
2320    ///
2321    /// There are 2 kinds of tag prefixes:
2322    ///   - Local: Starts with a `!`, contains only URI chars (`!foo`)
2323    ///   - Global: Starts with a tag char, contains then URI chars (`!foo,2000:app/`)
2324    fn scan_tag_prefix(&mut self, start_mark: &Marker) -> Result<String, ScanError> {
2325        let mut string = String::new();
2326
2327        if self.input.look_ch() == '!' {
2328            // If we have a local tag, insert and skip `!`.
2329            string.push(self.input.peek());
2330            self.skip_non_blank();
2331        } else if !is_tag_char(self.input.peek()) {
2332            // Otherwise, check if the first global tag character is valid.
2333            return Err(ScanError::from_kind(
2334                *start_mark,
2335                ErrorKind::InvalidGlobalTagCharacter,
2336            ));
2337        } else if self.input.peek() == '%' {
2338            // If it is valid and an escape sequence, escape it.
2339            string.push(self.scan_uri_escapes(start_mark)?);
2340        } else {
2341            // Otherwise, push the first character.
2342            string.push(self.input.peek());
2343            self.skip_non_blank();
2344        }
2345
2346        while is_uri_char(self.input.look_ch()) {
2347            if self.input.peek() == '%' {
2348                string.push(self.scan_uri_escapes(start_mark)?);
2349            } else {
2350                string.push(self.input.peek());
2351                self.skip_non_blank();
2352            }
2353        }
2354
2355        Ok(string)
2356    }
2357
2358    /// Scan for a verbatim tag.
2359    ///
2360    /// The prefixing `!<` must _not_ have been skipped.
2361    fn scan_verbatim_tag(&mut self, start_mark: &Marker) -> Result<String, ScanError> {
2362        // Eat `!<`
2363        self.skip_non_blank();
2364        self.skip_non_blank();
2365
2366        let mut string = String::new();
2367        while is_uri_char(self.input.look_ch()) {
2368            if self.input.peek() == '%' {
2369                string.push(self.scan_uri_escapes(start_mark)?);
2370            } else {
2371                string.push(self.input.peek());
2372                self.skip_non_blank();
2373            }
2374        }
2375
2376        if string.is_empty() {
2377            return Err(ScanError::from_kind(*start_mark, ErrorKind::MissingTagUri));
2378        }
2379
2380        if self.input.peek() != '>' {
2381            return Err(ScanError::from_kind(
2382                *start_mark,
2383                ErrorKind::UnclosedVerbatimTag,
2384            ));
2385        }
2386        self.skip_non_blank();
2387
2388        Ok(string)
2389    }
2390
2391    fn scan_tag_shorthand_suffix(
2392        &mut self,
2393        _directive: bool,
2394        _is_secondary: bool,
2395        head: &str,
2396        mark: &Marker,
2397    ) -> Result<String, ScanError> {
2398        let mut length = head.len();
2399        let mut string = String::new();
2400
2401        // Copy the head if needed.
2402        // Note that we don't copy the leading '!' character.
2403        if length > 1 {
2404            string.extend(head.chars().skip(1));
2405        }
2406
2407        while is_tag_char(self.input.look_ch()) {
2408            // Check if it is a URI-escape sequence.
2409            if self.input.peek() == '%' {
2410                string.push(self.scan_uri_escapes(mark)?);
2411            } else {
2412                string.push(self.input.peek());
2413                self.skip_non_blank();
2414            }
2415
2416            length += 1;
2417        }
2418
2419        if length == 0 {
2420            return Err(ScanError::from_kind(*mark, ErrorKind::MissingTagUri));
2421        }
2422
2423        Ok(string)
2424    }
2425
2426    fn scan_uri_escapes(&mut self, mark: &Marker) -> Result<char, ScanError> {
2427        let mut width = 0usize;
2428        let mut bytes = [0u8; 4];
2429        let mut bytes_len = 0usize;
2430        loop {
2431            self.input.lookahead(3);
2432
2433            let c = self.input.peek_nth(1);
2434            let nc = self.input.peek_nth(2);
2435
2436            if !(self.input.peek() == '%' && is_hex(c) && is_hex(nc)) {
2437                return Err(ScanError::from_kind(*mark, ErrorKind::InvalidTagEscape));
2438            }
2439
2440            let byte = u8::try_from((as_hex(c) << 4) + as_hex(nc))
2441                .expect("two hex nibbles always fit in a byte");
2442            if width == 0 {
2443                width = match byte {
2444                    _ if byte & 0x80 == 0x00 => 1,
2445                    _ if byte & 0xE0 == 0xC0 => 2,
2446                    _ if byte & 0xF0 == 0xE0 => 3,
2447                    _ if byte & 0xF8 == 0xF0 => 4,
2448                    _ => {
2449                        return Err(ScanError::from_kind(
2450                            *mark,
2451                            ErrorKind::InvalidTagUtf8LeadingByte,
2452                        ));
2453                    }
2454                };
2455            } else if byte & 0xc0 != 0x80 {
2456                return Err(ScanError::from_kind(
2457                    *mark,
2458                    ErrorKind::InvalidTagUtf8TrailingByte,
2459                ));
2460            }
2461
2462            bytes[bytes_len] = byte;
2463            bytes_len += 1;
2464
2465            self.skip_n_non_blank(3);
2466
2467            width -= 1;
2468            if width == 0 {
2469                break;
2470            }
2471        }
2472
2473        let s = core::str::from_utf8(&bytes[..bytes_len])
2474            .map_err(|_| ScanError::from_kind(*mark, ErrorKind::InvalidTagUtf8))?;
2475
2476        let Some(ch) = s.chars().next() else {
2477            unreachable!("a validated URI escape cannot decode to an empty string")
2478        };
2479        Ok(ch)
2480    }
2481
2482    fn fetch_anchor(&mut self, alias: bool) -> ScanResult {
2483        self.save_simple_key();
2484        self.disallow_simple_key();
2485
2486        let tok = self.scan_anchor(alias)?;
2487
2488        self.tokens.push_back(tok.into());
2489
2490        Ok(())
2491    }
2492
2493    fn scan_anchor(&mut self, alias: bool) -> Result<Token<'input>, ScanError> {
2494        let start_mark = self.mark;
2495
2496        // Skip `&` / `*`.
2497        self.skip_non_blank();
2498
2499        // Borrow from input when possible.
2500        if let Some(start) = self.input.byte_offset() {
2501            while is_anchor_char(self.input.look_ch()) {
2502                self.skip_non_blank();
2503            }
2504
2505            let end = self
2506                .input
2507                .byte_offset()
2508                .expect("byte_offset() must remain available once enabled");
2509
2510            if start == end {
2511                return Err(ScanError::from_kind(
2512                    start_mark,
2513                    ErrorKind::MissingAnchorOrAliasName,
2514                ));
2515            }
2516
2517            let cow = if let Some(slice) = self.try_borrow_slice(start, end) {
2518                Cow::Borrowed(slice)
2519            } else if let Some(slice) = self.input.slice_bytes(start, end) {
2520                Cow::Owned(slice.to_owned())
2521            } else {
2522                return Err(ScanError::from_kind(
2523                    start_mark,
2524                    ErrorKind::InputSlicingUnavailable,
2525                ));
2526            };
2527
2528            let tok = if alias {
2529                TokenType::Alias(cow)
2530            } else {
2531                TokenType::Anchor(cow)
2532            };
2533            return Ok(Token(Span::new(start_mark, self.mark), tok));
2534        }
2535
2536        let mut string = String::new();
2537        while is_anchor_char(self.input.look_ch()) {
2538            string.push(self.input.peek());
2539            self.skip_non_blank();
2540        }
2541
2542        if string.is_empty() {
2543            return Err(ScanError::from_kind(
2544                start_mark,
2545                ErrorKind::MissingAnchorOrAliasName,
2546            ));
2547        }
2548
2549        let tok = if alias {
2550            TokenType::Alias(string.into())
2551        } else {
2552            TokenType::Anchor(string.into())
2553        };
2554        Ok(Token(Span::new(start_mark, self.mark), tok))
2555    }
2556
2557    fn fetch_flow_collection_start(&mut self, tok: TokenType<'input>) -> ScanResult {
2558        // The indicators '[' and '{' may start a simple key.
2559        self.save_simple_key();
2560
2561        let start_mark = self.mark;
2562        let indicator = self.input.peek();
2563        self.flow_markers.push((start_mark, indicator));
2564
2565        self.roll_one_col_indent();
2566        self.increase_flow_level()?;
2567
2568        self.allow_simple_key();
2569
2570        self.skip_non_blank();
2571        let end_mark = self.mark;
2572
2573        if tok == TokenType::FlowMappingStart {
2574            self.flow_mapping_started.push(true);
2575        } else {
2576            self.flow_mapping_started.push(false);
2577            self.implicit_flow_mapping_states
2578                .push(ImplicitMappingState::Possible);
2579        }
2580
2581        let token_index = self.tokens.len();
2582        self.skip_ws_to_eol(SkipTabs::Yes)?;
2583
2584        self.insert_token(token_index, Token(Span::new(start_mark, end_mark), tok));
2585        Ok(())
2586    }
2587
2588    fn fetch_flow_collection_end(
2589        &mut self,
2590        tok: TokenType<'input>,
2591        expected_open: char,
2592        actual_close: char,
2593    ) -> ScanResult {
2594        // A closing bracket without a corresponding opening is invalid YAML.
2595        if self.flow_level == 0 {
2596            return Err(self.scan_error(ErrorKind::MisplacedFlowCollectionEnd));
2597        }
2598
2599        let Some((open_mark, open_ch)) = self.flow_markers.pop() else {
2600            return Err(self.scan_error(ErrorKind::MisplacedFlowCollectionEnd));
2601        };
2602
2603        if open_ch != expected_open {
2604            return Err(ScanError::from_kind(
2605                open_mark,
2606                ErrorKind::MismatchedFlowCollectionEnd {
2607                    open: open_ch,
2608                    close: actual_close,
2609                },
2610            ));
2611        }
2612
2613        let flow_level = self.flow_level;
2614
2615        self.remove_simple_key()?;
2616
2617        if matches!(tok, TokenType::FlowSequenceEnd) {
2618            self.end_implicit_mapping(self.mark, flow_level);
2619            // We are out exiting the flow sequence, nesting goes down 1 level.
2620            self.implicit_flow_mapping_states.pop();
2621        }
2622        self.flow_mapping_started.pop();
2623
2624        self.decrease_flow_level();
2625
2626        self.disallow_simple_key();
2627
2628        let start_mark = self.mark;
2629        self.skip_non_blank();
2630        let end_mark = self.mark;
2631        let token_index = self.tokens.len();
2632        self.skip_ws_to_eol(SkipTabs::Yes)?;
2633
2634        // A flow collection within a flow mapping can be a key. In that case, the value may be
2635        // adjacent to the `:`.
2636        // ```yaml
2637        // - [ {a: b}:value ]
2638        // ```
2639        if self.flow_level > 0 {
2640            self.adjacent_value_allowed_at = self.mark.index();
2641        }
2642
2643        self.insert_token(token_index, Token(Span::new(start_mark, end_mark), tok));
2644        Ok(())
2645    }
2646
2647    /// Push the `FlowEntry` token and skip over the `,`.
2648    fn fetch_flow_entry(&mut self) -> ScanResult {
2649        self.remove_simple_key()?;
2650        self.allow_simple_key();
2651
2652        self.end_implicit_mapping(self.mark, self.flow_level);
2653        if self.current_flow_collection_is_sequence() {
2654            self.set_current_flow_mapping_started(false);
2655        }
2656
2657        let start_mark = self.mark;
2658        self.skip_non_blank();
2659        let end_mark = self.mark;
2660        let token_index = self.tokens.len();
2661        self.skip_ws_to_eol(SkipTabs::Yes)?;
2662
2663        self.insert_token(
2664            token_index,
2665            Token(Span::new(start_mark, end_mark), TokenType::FlowEntry),
2666        );
2667        Ok(())
2668    }
2669
2670    fn increase_flow_level(&mut self) -> ScanResult {
2671        self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0)));
2672        self.flow_level = self
2673            .flow_level
2674            .checked_add(1)
2675            .ok_or_else(|| self.scan_error(ErrorKind::RecursionLimitExceeded))?;
2676        Ok(())
2677    }
2678
2679    fn decrease_flow_level(&mut self) {
2680        if self.flow_level > 0 {
2681            self.flow_level -= 1;
2682            self.simple_keys.pop().unwrap();
2683        }
2684    }
2685
2686    /// Push the `Block*` token(s) and skip over the `-`.
2687    ///
2688    /// Add an indentation level and push a `BlockSequenceStart` token if needed, then push a
2689    /// `BlockEntry` token.
2690    /// This function only skips over the `-` and does not fetch the entry value.
2691    fn fetch_block_entry(&mut self) -> ScanResult {
2692        if self.flow_level > 0 {
2693            // - * only allowed in block
2694            return Err(self.scan_error(ErrorKind::BlockEntryInFlowCollection));
2695        }
2696        // Check if we are allowed to start a new entry.
2697        if !self.simple_key_allowed {
2698            return Err(self.scan_error(ErrorKind::BlockSequenceEntryNotAllowed));
2699        }
2700
2701        // Skip over the `-`.
2702        let mark = self.mark;
2703        self.skip_non_blank();
2704
2705        // generate BLOCK-SEQUENCE-START if indented
2706        self.roll_indent(mark.col, None, TokenType::BlockSequenceStart, mark);
2707        let token_index = self.tokens.len();
2708        let found_tabs = self.skip_ws_to_eol(SkipTabs::Yes)?.found_tabs();
2709        self.input.lookahead(2);
2710        if found_tabs && self.input.next_char_is('-') && is_blank_or_breakz(self.input.peek_nth(1))
2711        {
2712            return Err(self.scan_error(ErrorKind::InvalidBlockEntryWhitespace));
2713        }
2714
2715        self.skip_ws_to_eol(SkipTabs::No)?;
2716        self.input.lookahead(1);
2717        if self.input.next_is_break() || self.input.next_is_flow() {
2718            self.roll_one_col_indent();
2719        }
2720
2721        self.remove_simple_key()?;
2722        self.allow_simple_key();
2723
2724        self.insert_token(
2725            token_index,
2726            Token(Span::empty(self.mark), TokenType::BlockEntry),
2727        );
2728
2729        Ok(())
2730    }
2731
2732    fn fetch_document_indicator(&mut self, t: TokenType<'input>) -> ScanResult {
2733        if let Some((mark, bracket)) = self.flow_markers.pop() {
2734            return Err(ScanError::from_kind(
2735                mark,
2736                ErrorKind::UnclosedFlowCollection { open: bracket },
2737            ));
2738        }
2739
2740        self.unroll_indent(-1);
2741        self.remove_simple_key()?;
2742        self.disallow_simple_key();
2743
2744        let mark = self.mark;
2745
2746        self.skip_n_non_blank(3);
2747
2748        self.document_prefix_allowed = matches!(t, TokenType::DocumentEnd);
2749        self.tokens
2750            .push_back(Token(Span::new(mark, self.mark), t).into());
2751        Ok(())
2752    }
2753
2754    fn fetch_block_scalar(&mut self, literal: bool) -> ScanResult {
2755        self.save_simple_key();
2756        self.allow_simple_key();
2757        let tok = self.scan_block_scalar(literal)?;
2758
2759        self.tokens.push_back(tok.into());
2760        Ok(())
2761    }
2762
2763    #[allow(clippy::too_many_lines)]
2764    fn scan_block_scalar(&mut self, literal: bool) -> Result<Token<'input>, ScanError> {
2765        let start_mark = self.mark;
2766        let mut chomping = Chomping::Clip;
2767        let mut increment: usize = 0;
2768        let mut indent: usize = 0;
2769        let mut trailing_blank: bool;
2770        let mut leading_blank: bool = false;
2771        let style = if literal {
2772            ScalarStyle::Literal
2773        } else {
2774            ScalarStyle::Folded
2775        };
2776
2777        let mut string = String::new();
2778        let mut leading_break = String::new();
2779        let mut trailing_breaks = String::new();
2780        let mut chomping_break = String::new();
2781
2782        // skip '|' or '>'
2783        self.skip_non_blank();
2784        self.unroll_non_block_indents();
2785
2786        if self.input.look_ch() == '+' || self.input.peek() == '-' {
2787            if self.input.peek() == '+' {
2788                chomping = Chomping::Keep;
2789            } else {
2790                chomping = Chomping::Strip;
2791            }
2792            self.skip_non_blank();
2793            self.input.lookahead(1);
2794            if self.input.next_is_digit() {
2795                if self.input.peek() == '0' {
2796                    return Err(ScanError::from_kind(
2797                        start_mark,
2798                        ErrorKind::ZeroBlockScalarIndent,
2799                    ));
2800                }
2801                increment = (self.input.peek() as usize) - ('0' as usize);
2802                self.skip_non_blank();
2803            }
2804        } else if self.input.next_is_digit() {
2805            if self.input.peek() == '0' {
2806                return Err(ScanError::from_kind(
2807                    start_mark,
2808                    ErrorKind::ZeroBlockScalarIndent,
2809                ));
2810            }
2811
2812            increment = (self.input.peek() as usize) - ('0' as usize);
2813            self.skip_non_blank();
2814            self.input.lookahead(1);
2815            if self.input.peek() == '+' || self.input.peek() == '-' {
2816                if self.input.peek() == '+' {
2817                    chomping = Chomping::Keep;
2818                } else {
2819                    chomping = Chomping::Strip;
2820                }
2821                self.skip_non_blank();
2822            }
2823        }
2824
2825        self.skip_ws_to_eol(SkipTabs::Yes)?;
2826
2827        // Check if we are at the end of the line.
2828        self.input.lookahead(1);
2829        self.ensure_current_char_is_printable()?;
2830        if !self.input.next_is_breakz() {
2831            return Err(ScanError::from_kind(
2832                start_mark,
2833                ErrorKind::InvalidBlockScalarHeader,
2834            ));
2835        }
2836
2837        if self.input.next_is_break() {
2838            self.input.lookahead(2);
2839            self.read_break(&mut chomping_break);
2840        }
2841
2842        self.ensure_current_char_is_printable()?;
2843
2844        if self.input.look_ch() == '\t' {
2845            return Err(ScanError::from_kind(
2846                start_mark,
2847                ErrorKind::TabAtBlockScalarStart,
2848            ));
2849        }
2850
2851        if increment > 0 {
2852            indent = if self.indent >= 0 {
2853                (self.indent + increment as isize) as usize
2854            } else {
2855                increment
2856            }
2857        }
2858
2859        // Scan the leading line breaks and determine the indentation level if needed.
2860        if indent == 0 {
2861            self.skip_block_scalar_first_line_indent(&mut indent, &mut trailing_breaks);
2862        } else {
2863            self.skip_block_scalar_indent(indent, &mut trailing_breaks);
2864        }
2865
2866        self.ensure_current_char_is_printable()?;
2867
2868        // We have an end-of-stream with no content, e.g.:
2869        // ```yaml
2870        // - |+
2871        // ```
2872        if self.input.next_is_z() {
2873            let contents = match chomping {
2874                // We strip trailing line breaks. Nothing remains.
2875                Chomping::Strip => String::new(),
2876                // There was no newline after the chomping indicator.
2877                _ if self.mark.line == start_mark.line() => String::new(),
2878                // With no content lines, the header break is not scalar content.
2879                Chomping::Clip => String::new(),
2880                // An indented whitespace-only line at EOF is an empty content line.
2881                Chomping::Keep if trailing_breaks.is_empty() && self.mark.col > 0 => chomping_break,
2882                // Keep actual empty content lines, if any, but not the header break.
2883                Chomping::Keep => trailing_breaks,
2884            };
2885
2886            return Ok(Token(
2887                Span::new(start_mark, self.mark),
2888                TokenType::Scalar(style, contents.into()),
2889            ));
2890        }
2891
2892        if self.mark.col < indent && (self.mark.col as isize) > self.indent {
2893            if self.indent < 0 && self.mark.col == 0 {
2894                self.input.lookahead(4);
2895                if self.input.next_is_document_start()
2896                    || self.input.next_is_document_end()
2897                    || self.input.peek() == '#'
2898                {
2899                    // At the root level, an explicit indentation indicator can still yield an
2900                    // empty scalar when the next line is a document marker or comment.
2901                    // In this case, the scalar is terminated rather than under-indented.
2902                } else {
2903                    return Err(self.scan_error(ErrorKind::InvalidBlockScalarIndent));
2904                }
2905            } else {
2906                return Err(self.scan_error(ErrorKind::InvalidBlockScalarIndent));
2907            }
2908        }
2909
2910        let mut line_buffer = String::with_capacity(100);
2911        let start_mark = self.mark;
2912        while self.mark.col == indent && !self.input.next_is_z() {
2913            self.ensure_current_char_is_printable()?;
2914
2915            if indent == 0 {
2916                self.input.lookahead(4);
2917                if self.input.next_is_document_end() {
2918                    break;
2919                }
2920            }
2921
2922            // We are at the first content character of a content line.
2923            trailing_blank = self.input.next_is_blank();
2924            if !literal && !leading_break.is_empty() && !leading_blank && !trailing_blank {
2925                string.push_str(&trailing_breaks);
2926                if trailing_breaks.is_empty() {
2927                    string.push(' ');
2928                }
2929            } else {
2930                string.push_str(&leading_break);
2931                string.push_str(&trailing_breaks);
2932            }
2933
2934            leading_break.clear();
2935            trailing_breaks.clear();
2936
2937            leading_blank = self.input.next_is_blank();
2938
2939            self.scan_block_scalar_content_line(&mut string, &mut line_buffer);
2940
2941            // break on EOF
2942            self.input.lookahead(2);
2943            if self.input.next_is_z() {
2944                break;
2945            }
2946
2947            self.ensure_current_char_is_printable()?;
2948
2949            self.read_break(&mut leading_break);
2950
2951            // Eat the following indentation spaces and line breaks.
2952            self.skip_block_scalar_indent(indent, &mut trailing_breaks);
2953        }
2954
2955        self.ensure_current_char_is_printable()?;
2956
2957        // Chomp the tail.
2958        if chomping != Chomping::Strip {
2959            string.push_str(&leading_break);
2960            // If we had reached an eof but the last character wasn't an end-of-line, check if the
2961            // last line was indented at least as the rest of the scalar, then we need to consider
2962            // there is a newline.
2963            if self.input.next_is_z() && self.mark.col >= indent.max(1) {
2964                string.push('\n');
2965            }
2966        }
2967
2968        if chomping == Chomping::Keep {
2969            string.push_str(&trailing_breaks);
2970        }
2971
2972        if let Some(character) = string.chars().find(|&character| !is_printable(character)) {
2973            return Err(ScanError::from_kind(
2974                start_mark,
2975                ErrorKind::UnexpectedCharacter { character },
2976            ));
2977        }
2978
2979        let span = if string.trim().is_empty() {
2980            Span::new(start_mark, self.mark)
2981        } else {
2982            Span::new(start_mark, self.mark).with_indent(Some(indent))
2983        };
2984
2985        Ok(Token(span, TokenType::Scalar(style, string.into())))
2986    }
2987
2988    /// Retrieve the contents of the line, parsing it as a block scalar.
2989    ///
2990    /// The contents will be appended to `string`. `line_buffer` is used as a temporary buffer to
2991    /// store bytes before pushing them to `string` and thus avoiding reallocating more than
2992    /// necessary. `line_buffer` is assumed to be empty upon calling this function. It will be
2993    /// `clear`ed before the end of the function.
2994    ///
2995    /// This function assumes the first character to read is the first content character in the
2996    /// line. This function does not consume the line break character(s) after the line.
2997    fn scan_block_scalar_content_line(&mut self, string: &mut String, line_buffer: &mut String) {
2998        // Start by evaluating characters in the buffer.
2999        while !self.input.buf_is_empty() && !self.input.next_is_breakz() {
3000            string.push(self.input.peek());
3001            // We may technically skip non-blank characters. However, the only distinction is
3002            // to determine what is leading whitespace and what is not. Here, we read the
3003            // contents of the line until either EOF or a line break. We know we will not read
3004            // `self.leading_whitespace` until the end of the line, where it will be reset.
3005            // This allows us to call a slightly less expensive function.
3006            self.skip_blank();
3007        }
3008
3009        // All characters that were in the buffer were consumed. We need to check if more
3010        // follow.
3011        if self.input.buf_is_empty() {
3012            // We will read all consecutive non-breakz characters. We push them into a
3013            // temporary buffer. The main difference with going through `self.buffer` is that
3014            // characters are appended here as their real size (1B for ASCII, or up to 4 bytes for
3015            // UTF-8). We can then use the internal `line_buffer` `Vec` to push data into `string`
3016            // (using `String::push_str`).
3017
3018            // line_buffer is empty at this point so we can compute n_chars here as well
3019            let mut n_chars = 0;
3020            debug_assert!(line_buffer.is_empty());
3021            while let Some(c) = self.input.raw_read_non_breakz_ch() {
3022                line_buffer.push(c);
3023                n_chars += 1;
3024            }
3025
3026            // We need to manually update our position; we haven't called a `skip` function.
3027            self.mark.col += n_chars;
3028            self.mark.offsets.chars += n_chars;
3029            self.mark.offsets.bytes = self.input.byte_offset();
3030
3031            // We can now append our bytes to our `string`.
3032            string.reserve(line_buffer.len());
3033            string.push_str(line_buffer);
3034            // This clears the _contents_ without touching the _capacity_.
3035            line_buffer.clear();
3036        }
3037    }
3038
3039    /// Skip the block scalar indentation and empty lines.
3040    fn skip_block_scalar_indent(&mut self, indent: usize, breaks: &mut String) {
3041        loop {
3042            // Consume all spaces. Tabs cannot be used as indentation.
3043            if indent < self.input.bufmaxlen().saturating_sub(2) {
3044                self.input.lookahead(self.input.bufmaxlen());
3045                while self.mark.col < indent && self.input.peek() == ' ' {
3046                    self.skip_blank();
3047                }
3048            } else {
3049                loop {
3050                    self.input.lookahead(self.input.bufmaxlen());
3051                    while !self.input.buf_is_empty()
3052                        && self.mark.col < indent
3053                        && self.input.peek() == ' '
3054                    {
3055                        self.skip_blank();
3056                    }
3057                    // If we reached our indent, we can break. We must also break if we have
3058                    // reached content or EOF; that is, the buffer is not empty and the next
3059                    // character is not a space.
3060                    if self.mark.col == indent
3061                        || (!self.input.buf_is_empty() && self.input.peek() != ' ')
3062                    {
3063                        break;
3064                    }
3065                }
3066                self.input.lookahead(2);
3067            }
3068
3069            // If our current line is empty, skip over the break and continue looping.
3070            if self.input.next_is_break() {
3071                self.read_break(breaks);
3072            } else {
3073                // Otherwise, we have a content line. Return control.
3074                break;
3075            }
3076        }
3077    }
3078
3079    /// Determine the indentation level for a block scalar from the first line of its contents.
3080    ///
3081    /// The function skips over whitespace-only lines and sets `indent` to the longest
3082    /// whitespace line that was encountered.
3083    fn skip_block_scalar_first_line_indent(&mut self, indent: &mut usize, breaks: &mut String) {
3084        let mut max_indent = 0;
3085        loop {
3086            // Consume all spaces. Tabs cannot be used as indentation.
3087            while self.input.look_ch() == ' ' {
3088                self.skip_blank();
3089            }
3090
3091            if self.mark.col > max_indent {
3092                max_indent = self.mark.col;
3093            }
3094
3095            if self.input.next_is_break() {
3096                // If our current line is empty, skip over the break and continue looping.
3097                self.input.lookahead(2);
3098                self.read_break(breaks);
3099            } else {
3100                // Otherwise, we have a content line. Return control.
3101                break;
3102            }
3103        }
3104
3105        // In case a YAML document looks like:
3106        // ```yaml
3107        // |
3108        // foo
3109        // bar
3110        // ```
3111        // We need to set the indent to 0 and not 1. In all other cases, the indent must be at
3112        // least 1. When in the above example, `self.indent` will be set to -1.
3113        *indent = max_indent.max((self.indent + 1) as usize);
3114    }
3115
3116    fn fetch_flow_scalar(&mut self, single: bool) -> ScanResult {
3117        self.save_simple_key();
3118        self.disallow_simple_key();
3119
3120        let token_index = self.tokens.len();
3121        let tok = self.scan_flow_scalar(single)?;
3122
3123        // From spec: To ensure JSON compatibility, if a key inside a flow mapping is JSON-like,
3124        // YAML allows the following value to be specified adjacent to the “:”.
3125        if self.skip_to_next_token(true)? {
3126            self.adjacent_value_allowed_at = usize::MAX;
3127        } else {
3128            self.adjacent_value_allowed_at = self.mark.index();
3129        }
3130
3131        self.insert_token(token_index, tok);
3132        Ok(())
3133    }
3134
3135    #[allow(clippy::too_many_lines)]
3136    fn scan_flow_scalar(&mut self, single: bool) -> Result<Token<'input>, ScanError> {
3137        let start_mark = self.mark;
3138
3139        // Output scalar contents.
3140        let mut buf = match self.input.byte_offset() {
3141            Some(off) => FlowScalarBuf::new_borrowed(off + self.input.peek().len_utf8()),
3142            None => FlowScalarBuf::new_owned(),
3143        };
3144
3145        // Scratch used to consume the *first* line break in a break run without emitting it.
3146        // (The first break folds to ' ' or to nothing depending on escaping rules.)
3147        let mut break_scratch = String::new();
3148
3149        /* Eat the left quote. */
3150        self.skip_non_blank();
3151
3152        loop {
3153            /* Check for a document indicator. */
3154            self.input.lookahead(4);
3155
3156            if self.mark.col == 0 && self.input.next_is_document_indicator() {
3157                return Err(ScanError::from_kind(
3158                    start_mark,
3159                    ErrorKind::DocumentIndicatorInQuotedScalar,
3160                ));
3161            }
3162
3163            if self.input.next_is_z() {
3164                return Err(ScanError::from_kind(
3165                    start_mark,
3166                    ErrorKind::UnclosedQuotedScalar,
3167                ));
3168            }
3169
3170            self.ensure_current_char_is_printable()?;
3171
3172            // Do not enforce block indentation inside quoted (flow) scalars.
3173            // YAML allows line breaks within quoted scalars.
3174            let mut leading_blanks = false;
3175            self.consume_flow_scalar_non_whitespace_chars(
3176                single,
3177                &mut buf,
3178                &mut leading_blanks,
3179                &start_mark,
3180            )?;
3181
3182            match self.input.look_ch() {
3183                '\'' if single => break,
3184                '"' if !single => break,
3185                _ => {}
3186            }
3187
3188            // --- Faster whitespace / line break handling (no temporary Strings) ---
3189            //
3190            // Instead of:
3191            //   - collecting blanks into `whitespaces` and then copying them
3192            //   - collecting breaks into `leading_break` / `trailing_breaks` and then copying
3193            //
3194            // We do:
3195            //   - append trailing blanks directly to `string`, remember where they started,
3196            //     and truncate them if a line break follows.
3197            //   - for line breaks: consume the first break into a scratch (discarded),
3198            //     append subsequent breaks directly to `string`.
3199            //
3200            // These flags replace temporary-string emptiness checks:
3201            //   has_leading_break  <=> !leading_break.is_empty()
3202            //   has_trailing_breaks <=> !trailing_breaks.is_empty()
3203            let mut trailing_ws_start: Option<usize> = None;
3204            let mut has_leading_break = false;
3205            let mut has_trailing_breaks = false;
3206
3207            // For the borrowed path: track the (byte) start of a pending whitespace run.
3208            let mut pending_ws_start: Option<usize> = None;
3209
3210            // Consume blank characters.
3211            while self.input.next_is_blank() || self.input.next_is_break() {
3212                if self.input.next_is_blank() {
3213                    // Consume a space or a tab character.
3214                    if leading_blanks {
3215                        if self.input.peek() == '\t' && (self.mark.col as isize) < self.indent {
3216                            return Err(self.scan_error(ErrorKind::TabInIndentation));
3217                        }
3218                        self.skip_blank();
3219                    } else {
3220                        // Append to output immediately; if a break appears next, we'll truncate.
3221                        match buf {
3222                            FlowScalarBuf::Owned(ref mut string) => {
3223                                if trailing_ws_start.is_none() {
3224                                    trailing_ws_start = Some(string.len());
3225                                }
3226                                string.push(self.input.peek());
3227                            }
3228                            FlowScalarBuf::Borrowed { .. } => {
3229                                if pending_ws_start.is_none() {
3230                                    pending_ws_start = self.input.byte_offset();
3231                                }
3232                            }
3233                        }
3234                        self.skip_blank();
3235
3236                        if let (FlowScalarBuf::Borrowed { .. }, Some(ws_start), Some(ws_end)) =
3237                            (&mut buf, pending_ws_start, self.input.byte_offset())
3238                        {
3239                            buf.note_pending_ws(ws_start, ws_end);
3240                        }
3241                    }
3242                } else {
3243                    self.input.lookahead(2);
3244
3245                    // Check if it is a first line break.
3246                    if leading_blanks {
3247                        // Second+ line break in a run: preserve it.
3248                        match buf {
3249                            FlowScalarBuf::Owned(ref mut string) => self.read_break(string),
3250                            FlowScalarBuf::Borrowed { .. } => {
3251                                self.promote_flow_scalar_buf_to_owned(&start_mark, &mut buf)?;
3252                                let Some(string) = buf.as_owned_mut() else {
3253                                    unreachable!()
3254                                };
3255                                self.read_break(string);
3256                            }
3257                        }
3258                        has_trailing_breaks = true;
3259                    } else {
3260                        // First break: drop any trailing blanks we appended, then consume the break.
3261                        if let Some(pos) = trailing_ws_start.take() {
3262                            if let FlowScalarBuf::Owned(ref mut string) = buf {
3263                                string.truncate(pos);
3264                            }
3265                        }
3266
3267                        if pending_ws_start.take().is_some() {
3268                            // Trailing blanks before a break are discarded => transformation.
3269                            if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3270                                self.promote_flow_scalar_buf_to_owned(&start_mark, &mut buf)?;
3271                            }
3272                            buf.discard_pending_ws();
3273                        } else {
3274                            buf.commit_pending_ws();
3275                        }
3276
3277                        break_scratch.clear();
3278                        self.read_break(&mut break_scratch);
3279                        // Keep `break_scratch` content (ignored) until next clear; no need to clear twice.
3280
3281                        has_leading_break = true;
3282                        leading_blanks = true;
3283                    }
3284                }
3285
3286                self.input.lookahead(1);
3287            }
3288
3289            // If we had a line break inside a quoted (flow) scalar, validate indentation
3290            // of the continuation line in block context.
3291            if leading_blanks && has_leading_break && self.flow_level == 0 {
3292                let next_ch = self.input.peek();
3293                let is_closing_quote = (single && next_ch == '\'') || (!single && next_ch == '"');
3294                if !is_closing_quote && (self.mark.col as isize) <= self.indent {
3295                    return Err(self.scan_error(ErrorKind::InvalidQuotedScalarIndent));
3296                }
3297            }
3298
3299            // Join the whitespace or fold line breaks.
3300            if leading_blanks {
3301                // Folding rule:
3302                //   if there was no leading break, preserve the pending whitespace already emitted
3303                //   if there was a leading break but no trailing breaks, fold to one space
3304                //   otherwise, preserve the trailing breaks already emitted
3305                if has_leading_break && !has_trailing_breaks {
3306                    match buf {
3307                        FlowScalarBuf::Owned(ref mut string) => string.push(' '),
3308                        FlowScalarBuf::Borrowed { .. } => {
3309                            self.promote_flow_scalar_buf_to_owned(&start_mark, &mut buf)?;
3310                            let Some(string) = buf.as_owned_mut() else {
3311                                unreachable!()
3312                            };
3313                            string.push(' ');
3314                        }
3315                    }
3316                }
3317            }
3318            // else: trailing blanks are already appended to `string`
3319        } // loop
3320
3321        // Eat the right quote.
3322        self.skip_non_blank();
3323        let end_mark = self.mark;
3324
3325        // Ensure there is no invalid trailing content.
3326        self.skip_ws_to_eol(SkipTabs::Yes)?;
3327        self.ensure_current_char_is_printable()?;
3328        match self.input.peek() {
3329            // These can be encountered in flow sequences or mappings.
3330            ',' | '}' | ']' if self.flow_level > 0 => {}
3331            // An end-of-line / end-of-stream is fine. No trailing content.
3332            c if is_breakz(c) => {}
3333            // ':' can be encountered if our scalar is a key.
3334            // Outside of flow contexts, keys cannot span multiple lines
3335            ':' if self.flow_level == 0 && start_mark.line == self.mark.line => {}
3336            // Inside a flow context, this is allowed.
3337            ':' if self.flow_level > 0 => {}
3338            _ => {
3339                let kind = if single {
3340                    ErrorKind::InvalidTrailingSingleQuotedScalar
3341                } else {
3342                    ErrorKind::InvalidTrailingDoubleQuotedScalar
3343                };
3344                return Err(self.scan_error(kind));
3345            }
3346        }
3347
3348        let style = if single {
3349            ScalarStyle::SingleQuoted
3350        } else {
3351            ScalarStyle::DoubleQuoted
3352        };
3353
3354        let contents = match buf {
3355            FlowScalarBuf::Owned(string) => Cow::Owned(string),
3356            FlowScalarBuf::Borrowed {
3357                start,
3358                mut end,
3359                pending_ws_start,
3360                pending_ws_end,
3361            } => {
3362                // If we ended after a whitespace run, it is part of the output (no break followed).
3363                if pending_ws_start.is_some() {
3364                    end = pending_ws_end;
3365                }
3366                if let Some(slice) = self.try_borrow_slice(start, end) {
3367                    Cow::Borrowed(slice)
3368                } else {
3369                    let slice = self.input.slice_bytes(start, end).ok_or_else(|| {
3370                        ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3371                    })?;
3372                    Cow::Owned(slice.to_owned())
3373                }
3374            }
3375        };
3376
3377        Ok(Token(
3378            Span::new(start_mark, end_mark),
3379            TokenType::Scalar(style, contents),
3380        ))
3381    }
3382
3383    /// Consume successive non-whitespace characters from a flow scalar.
3384    ///
3385    /// This function resolves escape sequences and stops upon encountering a whitespace, the end
3386    /// of the stream or the closing character for the scalar (`'` for single quoted scalars, `"`
3387    /// for double quoted scalars).
3388    ///
3389    /// # Errors
3390    /// Return an error if an invalid escape sequence is found.
3391    fn consume_flow_scalar_non_whitespace_chars(
3392        &mut self,
3393        single: bool,
3394        buf: &mut FlowScalarBuf,
3395        leading_blanks: &mut bool,
3396        start_mark: &Marker,
3397    ) -> Result<(), ScanError> {
3398        self.input.lookahead(2);
3399        while !is_blank_or_breakz(self.input.peek()) && is_printable(self.input.peek()) {
3400            match self.input.peek() {
3401                // Check for an escaped single quote.
3402                '\'' if self.input.peek_nth(1) == '\'' && single => {
3403                    if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3404                        buf.commit_pending_ws();
3405                        self.promote_flow_scalar_buf_to_owned(start_mark, buf)?;
3406                    }
3407                    let Some(string) = buf.as_owned_mut() else {
3408                        unreachable!()
3409                    };
3410                    string.push('\'');
3411                    self.skip_n_non_blank(2);
3412                }
3413                // Check for the right quote.
3414                '\'' if single => break,
3415                '"' if !single => break,
3416                // Check for an escaped line break.
3417                '\\' if !single && is_break(self.input.peek_nth(1)) => {
3418                    self.input.lookahead(3);
3419                    if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3420                        buf.commit_pending_ws();
3421                        self.promote_flow_scalar_buf_to_owned(start_mark, buf)?;
3422                    }
3423                    self.skip_non_blank();
3424                    self.skip_linebreak();
3425                    *leading_blanks = true;
3426                    break;
3427                }
3428                // Check for an escape sequence.
3429                '\\' if !single => {
3430                    if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3431                        buf.commit_pending_ws();
3432                        self.promote_flow_scalar_buf_to_owned(start_mark, buf)?;
3433                    }
3434                    let Some(string) = buf.as_owned_mut() else {
3435                        unreachable!()
3436                    };
3437                    string.push(self.resolve_flow_scalar_escape_sequence(start_mark)?);
3438                }
3439                c => {
3440                    match buf {
3441                        FlowScalarBuf::Owned(ref mut string) => {
3442                            string.push(c);
3443                        }
3444                        FlowScalarBuf::Borrowed { .. } => {
3445                            buf.commit_pending_ws();
3446                        }
3447                    }
3448                    self.skip_non_blank();
3449
3450                    if let Some(new_end) = self.input.byte_offset() {
3451                        if let FlowScalarBuf::Borrowed { end, .. } = buf {
3452                            *end = new_end;
3453                        }
3454                    }
3455                }
3456            }
3457            self.input.lookahead(2);
3458        }
3459        Ok(())
3460    }
3461
3462    /// Escape the sequence we encounter in a flow scalar.
3463    ///
3464    /// `self.input.peek()` must point to the `\` starting the escape sequence.
3465    ///
3466    /// # Errors
3467    /// Return an error if an invalid escape sequence is found.
3468    fn resolve_flow_scalar_escape_sequence(
3469        &mut self,
3470        start_mark: &Marker,
3471    ) -> Result<char, ScanError> {
3472        let mut code_length = 0usize;
3473        let mut ret = '\0';
3474
3475        match self.input.peek_nth(1) {
3476            '0' => ret = '\0',
3477            'a' => ret = '\x07',
3478            'b' => ret = '\x08',
3479            't' | '\t' => ret = '\t',
3480            'n' => ret = '\n',
3481            'v' => ret = '\x0b',
3482            'f' => ret = '\x0c',
3483            'r' => ret = '\x0d',
3484            'e' => ret = '\x1b',
3485            ' ' => ret = '\x20',
3486            '"' => ret = '"',
3487            '/' => ret = '/',
3488            '\\' => ret = '\\',
3489            // Unicode next line (#x85)
3490            'N' => ret = char::from_u32(0x85).unwrap(),
3491            // Unicode non-breaking space (#xA0)
3492            '_' => ret = char::from_u32(0xA0).unwrap(),
3493            // Unicode line separator (#x2028)
3494            'L' => ret = char::from_u32(0x2028).unwrap(),
3495            // Unicode paragraph separator (#x2029)
3496            'P' => ret = char::from_u32(0x2029).unwrap(),
3497            'x' => code_length = 2,
3498            'u' => code_length = 4,
3499            'U' => code_length = 8,
3500            _ => {
3501                return Err(ScanError::from_kind(
3502                    *start_mark,
3503                    ErrorKind::UnknownQuotedScalarEscape,
3504                ))
3505            }
3506        }
3507        self.skip_n_non_blank(2);
3508
3509        // Consume an arbitrary escape code.
3510        if code_length > 0 {
3511            self.input.lookahead(code_length);
3512            let mut value = 0u32;
3513            for i in 0..code_length {
3514                let c = self.input.peek_nth(i);
3515                if !is_hex(c) {
3516                    return Err(ScanError::from_kind(
3517                        *start_mark,
3518                        ErrorKind::InvalidQuotedScalarHexEscape,
3519                    ));
3520                }
3521                value = (value << 4) + as_hex(c);
3522            }
3523
3524            self.skip_n_non_blank(code_length);
3525
3526            // Handle JSON surrogate pairs: high surrogate followed by low surrogate
3527            if code_length == 4 && (0xD800..=0xDBFF).contains(&value) {
3528                self.input.lookahead(2);
3529                if self.input.peek() == '\\' && self.input.peek_nth(1) == 'u' {
3530                    self.skip_n_non_blank(2);
3531                    self.input.lookahead(4);
3532                    let mut low_value = 0u32;
3533                    for i in 0..4 {
3534                        let c = self.input.peek_nth(i);
3535                        if !is_hex(c) {
3536                            return Err(ScanError::from_kind(
3537                                *start_mark,
3538                                ErrorKind::InvalidLowSurrogateHexEscape,
3539                            ));
3540                        }
3541                        low_value = (low_value << 4) + as_hex(c);
3542                    }
3543                    if (0xDC00..=0xDFFF).contains(&low_value) {
3544                        value = 0x10000 + (((value - 0xD800) << 10) | (low_value - 0xDC00));
3545                        self.skip_n_non_blank(4);
3546                    } else {
3547                        return Err(ScanError::from_kind(
3548                            *start_mark,
3549                            ErrorKind::InvalidLowSurrogate,
3550                        ));
3551                    }
3552                } else {
3553                    return Err(ScanError::from_kind(
3554                        *start_mark,
3555                        ErrorKind::MissingLowSurrogate,
3556                    ));
3557                }
3558            } else if code_length == 4 && (0xDC00..=0xDFFF).contains(&value) {
3559                return Err(ScanError::from_kind(
3560                    *start_mark,
3561                    ErrorKind::UnpairedLowSurrogate,
3562                ));
3563            }
3564
3565            let Some(ch) = char::from_u32(value) else {
3566                return Err(ScanError::from_kind(
3567                    *start_mark,
3568                    ErrorKind::InvalidUnicodeEscape,
3569                ));
3570            };
3571            ret = ch;
3572        }
3573        Ok(ret)
3574    }
3575
3576    fn fetch_plain_scalar(&mut self) -> ScanResult {
3577        self.save_simple_key();
3578        self.disallow_simple_key();
3579
3580        let token_index = self.tokens.len();
3581        let tok = self.scan_plain_scalar()?;
3582
3583        self.insert_token(token_index, tok);
3584        Ok(())
3585    }
3586
3587    /// Scan for a plain scalar.
3588    ///
3589    /// Plain scalars are the most readable but restricted style. They may span multiple lines in
3590    /// some contexts.
3591    #[allow(clippy::too_many_lines)]
3592    fn scan_plain_scalar(&mut self) -> Result<Token<'input>, ScanError> {
3593        self.unroll_non_block_indents();
3594        let indent = self.indent + 1;
3595        let start_mark = self.mark;
3596
3597        if self.flow_level > 0 && (start_mark.col as isize) < indent {
3598            return Err(ScanError::from_kind(
3599                start_mark,
3600                ErrorKind::InvalidFlowScalarIndent,
3601            ));
3602        }
3603
3604        let borrow_start = start_mark
3605            .byte_offset()
3606            .filter(|start| self.try_borrow_slice(*start, *start).is_some());
3607        let mut string = borrow_start.is_none().then(|| String::with_capacity(32));
3608        let mut has_content = false;
3609        self.buf_whitespaces.clear();
3610        self.buf_leading_break.clear();
3611        self.buf_trailing_breaks.clear();
3612        let mut end_mark = self.mark;
3613
3614        loop {
3615            self.input.lookahead(4);
3616            if (self.mark.col == 0 && self.input.next_is_document_indicator())
3617                || self.input.peek() == '#'
3618            {
3619                // BS4K: If a `#` starts a comment after some separation spaces following content
3620                // of a plain scalar in block context, and there is potential continuation on the
3621                // next line, this is invalid. We cannot decide yet if there will be continuation,
3622                // so record that a comment interrupted a plain scalar.
3623                if self.input.peek() == '#'
3624                    && has_content
3625                    && !self.buf_whitespaces.is_empty()
3626                    && self.flow_level == 0
3627                {
3628                    self.interrupted_plain_by_comment = Some(self.mark);
3629                }
3630                break;
3631            }
3632
3633            if self.flow_level > 0 && self.input.peek() == '-' && is_flow(self.input.peek_nth(1)) {
3634                return Err(self.scan_error(ErrorKind::PlainScalarStartsWithDashFlowIndicator));
3635            }
3636
3637            if !self.input.next_is_blank_or_breakz()
3638                && self.input.next_can_be_plain_scalar(self.flow_level > 0)
3639            {
3640                if self.leading_whitespace {
3641                    if has_content && string.is_none() {
3642                        let start = borrow_start.expect("borrowed scalar has a start offset");
3643                        let end = end_mark.byte_offset().ok_or_else(|| {
3644                            ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3645                        })?;
3646                        let prefix = self.try_borrow_slice(start, end).ok_or_else(|| {
3647                            ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3648                        })?;
3649                        string = Some(prefix.to_owned());
3650                    }
3651                    if self.buf_leading_break.is_empty() {
3652                        if let Some(output) = string.as_mut() {
3653                            output.push_str(&self.buf_leading_break);
3654                            output.push_str(&self.buf_trailing_breaks);
3655                        }
3656                        self.buf_trailing_breaks.clear();
3657                        self.buf_leading_break.clear();
3658                    } else {
3659                        if self.buf_trailing_breaks.is_empty() {
3660                            if let Some(output) = string.as_mut() {
3661                                output.push(' ');
3662                            }
3663                        } else {
3664                            if let Some(output) = string.as_mut() {
3665                                output.push_str(&self.buf_trailing_breaks);
3666                            }
3667                            self.buf_trailing_breaks.clear();
3668                        }
3669                        self.buf_leading_break.clear();
3670                    }
3671                    self.leading_whitespace = false;
3672                } else if !self.buf_whitespaces.is_empty() {
3673                    if let Some(output) = string.as_mut() {
3674                        output.push_str(&self.buf_whitespaces);
3675                    }
3676                    self.buf_whitespaces.clear();
3677                }
3678
3679                // We can unroll the first iteration of the loop.
3680                has_content = true;
3681                if let Some(output) = string.as_mut() {
3682                    output.push(self.input.peek());
3683                }
3684                self.skip_non_blank();
3685                if let Some(output) = string.as_mut() {
3686                    output.reserve(self.input.bufmaxlen());
3687                }
3688
3689                // Add content non-blank characters to the scalar.
3690                let mut end = false;
3691                while !end {
3692                    // Fill the buffer once and process all characters in the buffer until the next
3693                    // fetch. `next_can_be_plain_scalar` needs 2 lookahead characters, so keep one
3694                    // spare slot for normal inputs while still forcing progress for very small
3695                    // custom buffer lengths.
3696                    self.input.lookahead(self.input.bufmaxlen());
3697                    let chunk_len = self.input.bufmaxlen().saturating_sub(1).max(1);
3698                    let (stop, chars_consumed) = if let Some(output) = string.as_mut() {
3699                        self.input
3700                            .fetch_plain_scalar_chunk(output, chunk_len, self.flow_level > 0)
3701                    } else {
3702                        self.input
3703                            .skip_plain_scalar_chunk(chunk_len, self.flow_level > 0)
3704                    };
3705                    end = stop;
3706                    self.mark.offsets.chars += chars_consumed;
3707                    self.mark.col += chars_consumed;
3708                    self.mark.offsets.bytes = self.input.byte_offset();
3709                }
3710                end_mark = self.mark;
3711            }
3712
3713            // We may reach the end of a plain scalar if:
3714            //  - We reach eof
3715            //  - We reach ": "
3716            //  - We find a flow character in a flow context
3717            if !(self.input.next_is_blank() || self.input.next_is_break()) {
3718                break;
3719            }
3720
3721            // Process blank characters.
3722            self.input.lookahead(2);
3723            while self.input.next_is_blank_or_break() {
3724                if self.input.next_is_blank() {
3725                    if !self.leading_whitespace {
3726                        self.buf_whitespaces.push(self.input.peek());
3727                        self.skip_blank();
3728                    } else if (self.mark.col as isize) < indent && self.input.peek() == '\t' {
3729                        // Tabs in an indentation columns are allowed if and only if the line is
3730                        // empty. Skip to the end of the line.
3731                        self.skip_ws_to_eol(SkipTabs::Yes)?;
3732                        if !self.input.next_is_breakz() {
3733                            return Err(ScanError::from_kind(
3734                                start_mark,
3735                                ErrorKind::TabInPlainScalar,
3736                            ));
3737                        }
3738                    } else {
3739                        self.skip_blank();
3740                    }
3741                } else {
3742                    // Check if it is a first line break
3743                    if self.leading_whitespace {
3744                        self.skip_break();
3745                        self.buf_trailing_breaks.push('\n');
3746                    } else {
3747                        self.buf_whitespaces.clear();
3748                        self.skip_break();
3749                        self.buf_leading_break.push('\n');
3750                        self.leading_whitespace = true;
3751                    }
3752                }
3753                self.input.lookahead(2);
3754            }
3755
3756            // check indentation level
3757            if self.flow_level == 0 && (self.mark.col as isize) < indent {
3758                break;
3759            }
3760        }
3761
3762        if self.leading_whitespace {
3763            self.allow_simple_key();
3764        }
3765
3766        let borrowed_contents = if string.is_none() {
3767            let start = borrow_start.expect("borrowed scalar has a start offset");
3768            let end = end_mark.byte_offset().ok_or_else(|| {
3769                ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3770            })?;
3771            Some(self.try_borrow_slice(start, end).ok_or_else(|| {
3772                ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3773            })?)
3774        } else {
3775            None
3776        };
3777        let scalar_text = borrowed_contents.unwrap_or_else(|| {
3778            string
3779                .as_deref()
3780                .expect("owned plain scalar has an output buffer")
3781        });
3782        if let Some(character) = scalar_text
3783            .chars()
3784            .find(|&character| !is_printable(character))
3785        {
3786            return Err(ScanError::from_kind(
3787                start_mark,
3788                ErrorKind::UnexpectedCharacter { character },
3789            ));
3790        }
3791        self.ensure_current_char_is_printable()?;
3792
3793        if has_content {
3794            let contents = if let Some(slice) = borrowed_contents {
3795                Cow::Borrowed(slice)
3796            } else {
3797                Cow::Owned(string.expect("owned plain scalar has an output buffer"))
3798            };
3799
3800            Ok(Token(
3801                Span::new(start_mark, end_mark),
3802                TokenType::Scalar(ScalarStyle::Plain, contents),
3803            ))
3804        } else {
3805            // `fetch_plain_scalar` must absolutely consume at least one byte. Otherwise,
3806            // `fetch_next_token` will never stop calling it. An empty plain scalar may happen with
3807            // erroneous inputs such as "{...".
3808            Err(ScanError::from_kind(
3809                start_mark,
3810                ErrorKind::UnexpectedEndOfPlainScalar,
3811            ))
3812        }
3813    }
3814
3815    fn fetch_key(&mut self) -> ScanResult {
3816        let start_mark = self.mark;
3817        if self.flow_level == 0 {
3818            // Check if we are allowed to start a new key (not necessarily simple).
3819            if !self.simple_key_allowed {
3820                return Err(self.scan_error(ErrorKind::MappingKeyNotAllowed));
3821            }
3822            self.roll_indent(
3823                start_mark.col,
3824                None,
3825                TokenType::BlockMappingStart,
3826                start_mark,
3827            );
3828        } else {
3829            // The scanner, upon emitting a `Key`, will prepend a `MappingStart` event.
3830            self.set_current_flow_mapping_started(true);
3831        }
3832
3833        self.remove_simple_key()?;
3834
3835        if self.flow_level == 0 {
3836            self.allow_simple_key();
3837        } else {
3838            self.disallow_simple_key();
3839        }
3840
3841        self.skip_non_blank();
3842        let end_mark = self.mark;
3843        let token_index = self.tokens.len();
3844        self.explicit_key_tab_check_pending = false;
3845        let stopped_after_comment = self.skip_yaml_whitespace(true)?;
3846        if self.input.peek() == '\t' {
3847            return Err(self.scan_error(ErrorKind::TabNotAllowed));
3848        }
3849        self.explicit_key_tab_check_pending = stopped_after_comment;
3850        self.insert_token(
3851            token_index,
3852            Token(Span::new(start_mark, end_mark), TokenType::Key),
3853        );
3854        Ok(())
3855    }
3856
3857    /// Fetch a value in a mapping inside of a flow collection.
3858    ///
3859    /// This must not be called if [`self.flow_level`] is 0. This ensures the rules surrounding
3860    /// values in flow collections are respected prior to calling [`fetch_value`].
3861    ///
3862    /// [`self.flow_level`]: Self::flow_level
3863    /// [`fetch_value`]: Self::fetch_value
3864    fn fetch_flow_value(&mut self) -> ScanResult {
3865        let nc = self.input.peek_nth(1);
3866
3867        // If we encounter a ':' inside a flow collection and it is not immediately
3868        // followed by a blank or breakz:
3869        //   - We must check whether an adjacent value is allowed
3870        //     `["a":[]]` is valid. If the key is double-quoted, no need for a space. This
3871        //     is needed for JSON compatibility.
3872        //   - If not, we must ensure there is a space after the ':' and before its value.
3873        //     `[a: []]` is valid while `[a:[]]` isn't. `[a:b]` is treated as `["a:b"]`.
3874        //   - But if the value is empty (null), then it's okay.
3875        // The last line is for YAMLs like `[a:]`. The ':' is followed by a ']' (which is a
3876        // flow character), but the ']' is not the value. The value is an invisible empty
3877        // space which is represented as null ('~').
3878        if self.mark.index() != self.adjacent_value_allowed_at && (nc == '[' || nc == '{') {
3879            return Err(self.scan_error(ErrorKind::FlowMappingValueAdjacentCollection));
3880        }
3881
3882        self.fetch_value()
3883    }
3884
3885    /// Fetch a value from a mapping (after a `:`).
3886    fn fetch_value(&mut self) -> ScanResult {
3887        let sk = *self.simple_keys.last().unwrap();
3888        let start_mark = self.mark;
3889        let is_implicit_flow_mapping = self.current_flow_collection_is_sequence()
3890            && !self.current_flow_mapping_started()
3891            && !self.implicit_flow_mapping_states.is_empty();
3892        if is_implicit_flow_mapping {
3893            *self.implicit_flow_mapping_states.last_mut().unwrap() =
3894                ImplicitMappingState::Inside(self.flow_level);
3895        }
3896
3897        // Skip over ':'.
3898        self.skip_non_blank();
3899        // Error detection: if ':' is followed by tab(s) without any space, and then what looks
3900        // like a value, emit a helpful error. The check for '-' or alphanumeric is an intentional
3901        // heuristic that catches common cases (e.g., `key:\tvalue`, `key:\t-item`) without
3902        // rejecting valid YAML like `key:\t|` (block scalar) or `key:\t"quoted"`.
3903        // Note: This heuristic won't catch Unicode value starters like `key:\täöü`, but such
3904        // cases will still fail to parse correctly (just with a less specific error message).
3905        let mut trailing_tokens = VecDeque::new();
3906        if self.input.look_ch() == '\t' {
3907            let trailing_token_index = self.tokens.len();
3908            let whitespace = self.skip_ws_to_eol(SkipTabs::Yes)?;
3909            trailing_tokens = self.tokens.split_off(trailing_token_index);
3910
3911            if !whitespace.has_valid_yaml_ws()
3912                && (self.input.peek() == '-' || self.input.next_is_alpha())
3913            {
3914                return Err(self.scan_error(ErrorKind::InvalidMappingValueWhitespace));
3915            }
3916        }
3917
3918        if sk.possible {
3919            let token_index = self.simple_key_token_index(&sk, start_mark)?;
3920            // insert simple key
3921            let tok = Token(Span::empty(sk.mark), TokenType::Key);
3922            self.insert_token(token_index, tok);
3923            if is_implicit_flow_mapping {
3924                if sk.mark.line < start_mark.line {
3925                    return Err(ScanError::from_kind(
3926                        start_mark,
3927                        ErrorKind::InvalidColonPlacement,
3928                    ));
3929                }
3930                self.insert_token(
3931                    token_index,
3932                    Token(Span::empty(sk.mark), TokenType::FlowMappingStart),
3933                );
3934            }
3935
3936            // Add the BLOCK-MAPPING-START token if needed.
3937            self.roll_indent(
3938                sk.mark.col,
3939                Some(sk.token_number),
3940                TokenType::BlockMappingStart,
3941                sk.mark,
3942            );
3943            self.roll_one_col_indent();
3944
3945            self.simple_keys.last_mut().unwrap().possible = false;
3946            self.disallow_simple_key();
3947        } else {
3948            if is_implicit_flow_mapping {
3949                self.tokens
3950                    .push_back(Token(Span::empty(start_mark), TokenType::FlowMappingStart).into());
3951            }
3952            // The ':' indicator follows a complex key.
3953            if self.flow_level == 0 {
3954                if !self.simple_key_allowed {
3955                    return Err(ScanError::from_kind(
3956                        start_mark,
3957                        ErrorKind::MappingValueNotAllowed,
3958                    ));
3959                }
3960
3961                self.roll_indent(
3962                    start_mark.col,
3963                    None,
3964                    TokenType::BlockMappingStart,
3965                    start_mark,
3966                );
3967            }
3968            self.roll_one_col_indent();
3969
3970            if self.flow_level == 0 {
3971                self.allow_simple_key();
3972            } else {
3973                self.disallow_simple_key();
3974            }
3975        }
3976        self.tokens
3977            .push_back(Token(Span::empty(start_mark), TokenType::Value).into());
3978        self.tokens.append(&mut trailing_tokens);
3979
3980        Ok(())
3981    }
3982
3983    /// Add an indentation level to the stack with the given block token, if needed.
3984    ///
3985    /// An indentation level is added only if:
3986    ///   - We are not in a flow-style construct (which don't have indentation per-se).
3987    ///   - The current column is further indented than the last indent we have registered.
3988    fn roll_indent(
3989        &mut self,
3990        col: usize,
3991        number: Option<usize>,
3992        tok: TokenType<'input>,
3993        mark: Marker,
3994    ) {
3995        if self.flow_level > 0 {
3996            return;
3997        }
3998
3999        // If the last indent was a non-block indent, remove it.
4000        // This means that we prepared an indent that we thought we wouldn't use, but realized just
4001        // now that it is a block indent.
4002        if self.indent <= col as isize {
4003            if let Some(indent) = self.indents.last() {
4004                if !indent.needs_block_end {
4005                    self.indent = indent.indent;
4006                    self.indents.pop();
4007                }
4008            }
4009        }
4010
4011        if self.indent < col as isize {
4012            self.indents.push(Indent {
4013                indent: self.indent,
4014                needs_block_end: true,
4015            });
4016            self.indent = col as isize;
4017            let tokens_parsed = self.tokens_parsed;
4018            match number {
4019                Some(n) => self.insert_token(n - tokens_parsed, Token(Span::empty(mark), tok)),
4020                None => self.tokens.push_back(Token(Span::empty(mark), tok).into()),
4021            }
4022        }
4023    }
4024
4025    /// Pop indentation levels from the stack as much as needed.
4026    ///
4027    /// Indentation levels are popped from the stack while they are further indented than `col`.
4028    /// If we are in a flow-style construct (which don't have indentation per-se), this function
4029    /// does nothing.
4030    fn unroll_indent(&mut self, col: isize) {
4031        if self.flow_level > 0 {
4032            return;
4033        }
4034        while self.indent > col {
4035            let indent = self.indents.pop().unwrap();
4036            self.indent = indent.indent;
4037            if indent.needs_block_end {
4038                self.tokens
4039                    .push_back(Token(Span::empty(self.mark), TokenType::BlockEnd).into());
4040            }
4041        }
4042    }
4043
4044    /// Add an indentation level of 1 column that does not start a block.
4045    ///
4046    /// See the documentation of [`Indent::needs_block_end`] for more details.
4047    /// An indentation is not added if we are inside a flow level or if the last indent is already
4048    /// a non-block indent.
4049    fn roll_one_col_indent(&mut self) {
4050        if self.flow_level == 0 && self.indents.last().is_some_and(|x| x.needs_block_end) {
4051            self.indents.push(Indent {
4052                indent: self.indent,
4053                needs_block_end: false,
4054            });
4055            self.indent += 1;
4056        }
4057    }
4058
4059    /// Unroll all last indents created with [`Self::roll_one_col_indent`].
4060    fn unroll_non_block_indents(&mut self) {
4061        while let Some(indent) = self.indents.last() {
4062            if indent.needs_block_end {
4063                break;
4064            }
4065            self.indent = indent.indent;
4066            self.indents.pop();
4067        }
4068    }
4069
4070    /// Mark the next token to be inserted as a potential simple key.
4071    fn save_simple_key(&mut self) {
4072        if self.simple_key_allowed {
4073            let required = self.flow_level == 0
4074                && self.indent == (self.mark.col as isize)
4075                && self.indents.last().unwrap().needs_block_end;
4076
4077            if let Some(last) = self.simple_keys.last_mut() {
4078                *last = SimpleKey {
4079                    mark: self.mark,
4080                    possible: true,
4081                    required,
4082                    token_number: self.tokens_parsed + self.tokens.len(),
4083                };
4084            }
4085        }
4086    }
4087
4088    fn remove_simple_key(&mut self) -> ScanResult {
4089        let last = self.simple_keys.last_mut().unwrap();
4090        if last.possible && last.required {
4091            return Err(Self::simple_key_expected(last.mark));
4092        }
4093
4094        last.possible = false;
4095        Ok(())
4096    }
4097
4098    /// Return whether the scanner is inside a block but outside of a flow sequence.
4099    fn is_within_block(&self) -> bool {
4100        !self.indents.is_empty()
4101    }
4102
4103    /// If an implicit mapping had started, end it.
4104    ///
4105    /// This function does not pop the state in [`implicit_flow_mapping_states`].
4106    ///
4107    /// [`implicit_flow_mapping_states`]: Self::implicit_flow_mapping_states
4108    fn end_implicit_mapping(&mut self, mark: Marker, flow_level: u8) {
4109        if self
4110            .implicit_flow_mapping_states
4111            .last()
4112            .is_some_and(|state| *state == ImplicitMappingState::Inside(flow_level))
4113        {
4114            *self.implicit_flow_mapping_states.last_mut().unwrap() = ImplicitMappingState::Possible;
4115            self.set_current_flow_mapping_started(false);
4116            self.tokens
4117                .push_back(Token(Span::empty(mark), TokenType::FlowMappingEnd).into());
4118        }
4119    }
4120
4121    fn current_flow_collection_is_sequence(&self) -> bool {
4122        self.flow_markers
4123            .last()
4124            .is_some_and(|(_, bracket)| *bracket == '[')
4125    }
4126
4127    fn current_flow_mapping_started(&self) -> bool {
4128        self.flow_mapping_started.last().copied().unwrap_or(false)
4129    }
4130
4131    fn set_current_flow_mapping_started(&mut self, started: bool) {
4132        if let Some(current) = self.flow_mapping_started.last_mut() {
4133            *current = started;
4134        }
4135    }
4136}
4137
4138/// Chomping, how final line breaks and trailing empty lines are interpreted.
4139///
4140/// See YAML spec 8.1.1.2.
4141#[derive(PartialEq, Eq)]
4142enum Chomping {
4143    /// The final line break and any trailing empty lines are excluded.
4144    Strip,
4145    /// The final line break is preserved, but trailing empty lines are excluded.
4146    Clip,
4147    /// The final line break and trailing empty lines are included.
4148    Keep,
4149}
4150
4151#[cfg(test)]
4152mod test {
4153    use alloc::{
4154        borrow::{Cow, ToOwned},
4155        rc::Rc,
4156        string::String,
4157        vec,
4158        vec::Vec,
4159    };
4160    use core::cell::Cell;
4161
4162    use crate::error::{ErrorKind, ScanError};
4163    use crate::{
4164        input::{str::StrInput, BorrowedInput, BufferedInput, Input},
4165        scanner::{
4166            Comment, Marker, Placement, QueuedToken, QueuedTokenType, ScalarStyle, Scanner, Span,
4167            Token, TokenType,
4168        },
4169    };
4170
4171    struct CountingChars {
4172        chars: alloc::vec::IntoIter<char>,
4173        read: Rc<Cell<usize>>,
4174    }
4175
4176    impl Iterator for CountingChars {
4177        type Item = char;
4178
4179        fn next(&mut self) -> Option<Self::Item> {
4180            let next = self.chars.next();
4181            if next.is_some() {
4182                self.read.set(self.read.get() + 1);
4183            }
4184            next
4185        }
4186    }
4187
4188    struct SlicingOnlyInput<'input> {
4189        inner: StrInput<'input>,
4190        expose_slice: bool,
4191    }
4192
4193    impl<'input> SlicingOnlyInput<'input> {
4194        fn new(source: &'input str, expose_slice: bool) -> Self {
4195            Self {
4196                inner: StrInput::new(source),
4197                expose_slice,
4198            }
4199        }
4200    }
4201
4202    impl Input for SlicingOnlyInput<'_> {
4203        fn lookahead(&mut self, count: usize) {
4204            self.inner.lookahead(count);
4205        }
4206
4207        fn buflen(&self) -> usize {
4208            self.inner.buflen()
4209        }
4210
4211        fn bufmaxlen(&self) -> usize {
4212            self.inner.bufmaxlen()
4213        }
4214
4215        fn raw_read_ch(&mut self) -> char {
4216            self.inner.raw_read_ch()
4217        }
4218
4219        fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
4220            self.inner.raw_read_non_breakz_ch()
4221        }
4222
4223        fn skip(&mut self) {
4224            self.inner.skip();
4225        }
4226
4227        fn skip_n(&mut self, count: usize) {
4228            self.inner.skip_n(count);
4229        }
4230
4231        fn peek(&self) -> char {
4232            self.inner.peek()
4233        }
4234
4235        fn peek_nth(&self, n: usize) -> char {
4236            self.inner.peek_nth(n)
4237        }
4238
4239        fn byte_offset(&self) -> Option<usize> {
4240            self.inner.byte_offset()
4241        }
4242
4243        fn slice_bytes(&self, start: usize, end: usize) -> Option<&str> {
4244            if self.expose_slice {
4245                self.inner.slice_bytes(start, end)
4246            } else {
4247                None
4248            }
4249        }
4250    }
4251
4252    impl<'input> BorrowedInput<'input> for SlicingOnlyInput<'input> {
4253        fn slice_borrowed(&self, _start: usize, _end: usize) -> Option<&'input str> {
4254            None
4255        }
4256    }
4257
4258    struct SmallReportedBufferInput<'input> {
4259        inner: StrInput<'input>,
4260        reported_bufmaxlen: usize,
4261    }
4262
4263    impl<'input> SmallReportedBufferInput<'input> {
4264        fn new(source: &'input str, reported_bufmaxlen: usize) -> Self {
4265            Self {
4266                inner: StrInput::new(source),
4267                reported_bufmaxlen,
4268            }
4269        }
4270    }
4271
4272    impl Input for SmallReportedBufferInput<'_> {
4273        fn lookahead(&mut self, count: usize) {
4274            self.inner.lookahead(count);
4275        }
4276
4277        fn buflen(&self) -> usize {
4278            self.inner.buflen()
4279        }
4280
4281        fn bufmaxlen(&self) -> usize {
4282            self.reported_bufmaxlen
4283        }
4284
4285        fn raw_read_ch(&mut self) -> char {
4286            self.inner.raw_read_ch()
4287        }
4288
4289        fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
4290            self.inner.raw_read_non_breakz_ch()
4291        }
4292
4293        fn skip(&mut self) {
4294            self.inner.skip();
4295        }
4296
4297        fn skip_n(&mut self, count: usize) {
4298            self.inner.skip_n(count);
4299        }
4300
4301        fn peek(&self) -> char {
4302            self.inner.peek()
4303        }
4304
4305        fn peek_nth(&self, n: usize) -> char {
4306            self.inner.peek_nth(n)
4307        }
4308    }
4309
4310    impl<'input> BorrowedInput<'input> for SmallReportedBufferInput<'input> {
4311        fn slice_borrowed(&self, start: usize, end: usize) -> Option<&'input str> {
4312            self.inner.slice_borrowed(start, end)
4313        }
4314    }
4315
4316    #[test]
4317    fn anchor_character_set_allows_colon_and_rejects_flow_indicators() {
4318        use super::is_anchor_char;
4319
4320        assert!(is_anchor_char('x'));
4321        assert!(is_anchor_char('-'));
4322        assert!(is_anchor_char('_'));
4323        assert!(is_anchor_char(':'));
4324        assert!(is_anchor_char('#'));
4325        assert!(is_anchor_char('/'));
4326        assert!(is_anchor_char('?'));
4327
4328        for c in [',', '[', ']', '{', '}', ' ', '\t', '\n', '\r', '\0'] {
4329            assert!(
4330                !is_anchor_char(c),
4331                "character {c:?} must not be accepted in anchor/alias names"
4332            );
4333        }
4334    }
4335
4336    #[test]
4337    fn flow_simple_key_length_limit_bounds_buffering() {
4338        let mut yaml = String::from("[\n\"start\"\n");
4339        for _ in 0..600 {
4340            yaml.push_str("\"x\"\n");
4341        }
4342        let total_chars = yaml.chars().count();
4343        let read = Rc::new(Cell::new(0));
4344        let chars = yaml.chars().collect::<Vec<_>>().into_iter();
4345        let mut scanner = Scanner::new(BufferedInput::new(CountingChars {
4346            chars,
4347            read: Rc::clone(&read),
4348        }));
4349
4350        assert!(matches!(
4351            scanner.next_token().unwrap().unwrap().1,
4352            TokenType::StreamStart
4353        ));
4354
4355        let token = scanner.next_token().unwrap().unwrap();
4356        assert!(matches!(token.1, TokenType::FlowSequenceStart));
4357
4358        let token = scanner.next_token().unwrap().unwrap();
4359        assert!(matches!(
4360            token.1,
4361            TokenType::Scalar(_, ref value) if value == "start"
4362        ));
4363        assert!(
4364            read.get() < total_chars,
4365            "scanner consumed all {total_chars} chars before yielding the first flow scalar"
4366        );
4367        assert!(
4368            read.get() <= super::SIMPLE_KEY_MAX_LOOKAHEAD + 128,
4369            "scanner read {} chars before yielding the first flow scalar",
4370            read.get()
4371        );
4372    }
4373
4374    #[test]
4375    fn block_scalar_indent_tolerates_small_reported_bufmaxlen() {
4376        let mut scanner = Scanner::new(SmallReportedBufferInput::new("|\n  value\n", 0));
4377
4378        let scalar = scanner
4379            .find_map(
4380                |token| match token.expect("valid YAML should scan without errors") {
4381                    Token(_, TokenType::Scalar(ScalarStyle::Literal, value)) => {
4382                        Some(value.into_owned())
4383                    }
4384                    _ => None,
4385                },
4386            )
4387            .expect("expected block scalar token");
4388
4389        assert_eq!(scalar, "value\n");
4390    }
4391
4392    #[test]
4393    fn plain_scalar_chunk_tolerates_small_reported_bufmaxlen() {
4394        let mut scanner = Scanner::new(SmallReportedBufferInput::new("plain\n", 0));
4395
4396        let scalar = scanner
4397            .find_map(
4398                |token| match token.expect("valid YAML should scan without errors") {
4399                    Token(_, TokenType::Scalar(ScalarStyle::Plain, value)) => {
4400                        Some(value.into_owned())
4401                    }
4402                    _ => None,
4403                },
4404            )
4405            .expect("expected plain scalar token");
4406
4407        assert_eq!(scalar, "plain");
4408    }
4409
4410    fn first_token_slice(
4411        yaml: &str,
4412        matches_token: impl Fn(&TokenType<'_>) -> bool,
4413    ) -> Option<String> {
4414        let mut scanner = Scanner::new(StrInput::new(yaml));
4415
4416        loop {
4417            let token = scanner
4418                .next_token()
4419                .expect("scanner should accept the test YAML")?;
4420            if matches_token(&token.1) {
4421                return token.0.slice(yaml).map(ToOwned::to_owned);
4422            }
4423        }
4424    }
4425
4426    #[test]
4427    fn flow_indicator_token_spans_cover_only_the_indicator() {
4428        assert_eq!(
4429            first_token_slice("[ # c\n  a]\n", |token| matches!(
4430                token,
4431                TokenType::FlowSequenceStart
4432            ))
4433            .as_deref(),
4434            Some("[")
4435        );
4436        assert_eq!(
4437            first_token_slice("{ # c\n  a: b}\n", |token| matches!(
4438                token,
4439                TokenType::FlowMappingStart
4440            ))
4441            .as_deref(),
4442            Some("{")
4443        );
4444        assert_eq!(
4445            first_token_slice("[a] # c\n", |token| matches!(
4446                token,
4447                TokenType::FlowSequenceEnd
4448            ))
4449            .as_deref(),
4450            Some("]")
4451        );
4452        assert_eq!(
4453            first_token_slice("{a: b} # c\n", |token| matches!(
4454                token,
4455                TokenType::FlowMappingEnd
4456            ))
4457            .as_deref(),
4458            Some("}")
4459        );
4460        assert_eq!(
4461            first_token_slice("[a, # c\nb]\n", |token| matches!(
4462                token,
4463                TokenType::FlowEntry
4464            ))
4465            .as_deref(),
4466            Some(",")
4467        );
4468    }
4469
4470    #[test]
4471    fn explicit_key_token_span_covers_only_the_indicator() {
4472        assert_eq!(
4473            first_token_slice("? # c\n: value\n", |token| matches!(token, TokenType::Key))
4474                .as_deref(),
4475            Some("?")
4476        );
4477    }
4478
4479    #[test]
4480    fn comment_capture_does_not_change_leading_whitespace() {
4481        let mut scanner = Scanner::new(StrInput::new("# comment\n"));
4482
4483        let token = scanner.scan_comment_token().unwrap();
4484
4485        assert!(scanner.leading_whitespace);
4486        assert!(matches!(token.1, TokenType::Comment(ref comment) if comment.text == " comment"));
4487
4488        let mut scanner = Scanner::new(BufferedInput::new("# streaming\n".chars()));
4489        scanner.input.lookahead(1);
4490
4491        let token = scanner.scan_comment_token().unwrap();
4492
4493        assert!(scanner.leading_whitespace);
4494        assert!(matches!(token.1, TokenType::Comment(ref comment) if comment.text == " streaming"));
4495    }
4496
4497    #[test]
4498    fn comment_capture_falls_back_to_owned_slice_when_borrow_unavailable() {
4499        let mut scanner = Scanner::new(SlicingOnlyInput::new("# sliced\n", true));
4500        scanner.input.lookahead(2);
4501        assert_eq!(scanner.input.peek_nth(1), ' ');
4502
4503        let token = scanner.scan_comment_token().unwrap();
4504
4505        assert!(matches!(token.1, TokenType::Comment(ref comment)
4506            if matches!(comment.text, Cow::Owned(ref text) if text == " sliced")));
4507    }
4508
4509    #[test]
4510    fn comment_capture_errors_when_offsets_have_no_slice() {
4511        let mut scanner = Scanner::new(SlicingOnlyInput::new("# broken\n", false));
4512
4513        let error = scanner.scan_comment_token().unwrap_err();
4514
4515        assert_eq!(error.kind(), &ErrorKind::InputOffsetsWithoutSlice);
4516    }
4517
4518    #[test]
4519    fn queued_token_roundtrips_public_token_variants() {
4520        let span = Span::new(Marker::new(0, 1, 0), Marker::new(7, 1, 7));
4521        let tokens = [
4522            Token(span, TokenType::StreamStart),
4523            Token(span, TokenType::StreamEnd),
4524            Token(span, TokenType::VersionDirective(1, 2)),
4525            Token(
4526                span,
4527                TokenType::TagDirective(Cow::Borrowed("!app!"), Cow::Borrowed("tag:app.example,")),
4528            ),
4529            Token(span, TokenType::DocumentStart),
4530            Token(span, TokenType::DocumentEnd),
4531            Token(span, TokenType::BlockSequenceStart),
4532            Token(span, TokenType::BlockMappingStart),
4533            Token(span, TokenType::BlockEnd),
4534            Token(span, TokenType::FlowSequenceStart),
4535            Token(span, TokenType::FlowSequenceEnd),
4536            Token(span, TokenType::FlowMappingStart),
4537            Token(span, TokenType::FlowMappingEnd),
4538            Token(span, TokenType::BlockEntry),
4539            Token(span, TokenType::FlowEntry),
4540            Token(span, TokenType::Key),
4541            Token(span, TokenType::Value),
4542            Token(span, TokenType::Alias(Cow::Borrowed("alias"))),
4543            Token(span, TokenType::Anchor(Cow::Borrowed("anchor"))),
4544            Token(
4545                span,
4546                TokenType::Tag(Cow::Borrowed("!"), Cow::Borrowed("tag")),
4547            ),
4548            Token(
4549                span,
4550                TokenType::Scalar(ScalarStyle::Literal, Cow::Borrowed("scalar")),
4551            ),
4552            Token(
4553                span,
4554                TokenType::Comment(
4555                    Comment::new(Cow::Borrowed(" comment")).with_placement(Placement::Right),
4556                ),
4557            ),
4558            Token(
4559                span,
4560                TokenType::ReservedDirective(
4561                    "reserved".to_owned(),
4562                    vec!["one".to_owned(), "two".to_owned()],
4563                ),
4564            ),
4565        ];
4566
4567        for token in tokens {
4568            let queued: QueuedToken = token.clone().into();
4569
4570            assert_eq!(queued.into_public(), token);
4571        }
4572    }
4573
4574    #[test]
4575    fn comment_skipping_path_consumes_comment_without_tokenizing_it() {
4576        let mut scanner = Scanner::new(StrInput::new("# skipped\nnext: value\n"));
4577
4578        scanner.skip_yaml_whitespace(false).unwrap();
4579
4580        assert!(scanner.tokens.is_empty());
4581        assert_eq!(scanner.mark.line(), 2);
4582        assert_eq!(scanner.mark.col(), 0);
4583    }
4584
4585    #[test]
4586    fn yaml_whitespace_can_stop_after_queued_comment() {
4587        let mut scanner = Scanner::new(StrInput::new(" # queued\n# later\n"));
4588
4589        assert!(scanner.skip_yaml_whitespace(true).unwrap());
4590
4591        assert_eq!(scanner.tokens.len(), 1);
4592        assert!(matches!(
4593            scanner.tokens.front().unwrap().1,
4594            QueuedTokenType::Comment(ref comment) if comment.text == " queued"
4595        ));
4596        assert_eq!(scanner.mark.line(), 1);
4597        assert_eq!(scanner.mark.col(), 9);
4598    }
4599
4600    #[test]
4601    fn token_skip_can_stop_after_queued_comment() {
4602        let mut scanner = Scanner::new(StrInput::new("# first\n# second\n"));
4603
4604        assert!(scanner.skip_to_next_token(true).unwrap());
4605
4606        assert_eq!(scanner.tokens.len(), 1);
4607        assert!(matches!(
4608            scanner.tokens.front().unwrap().1,
4609            QueuedTokenType::Comment(ref comment) if comment.text == " first"
4610        ));
4611        assert_eq!(scanner.mark.line(), 2);
4612        assert_eq!(scanner.mark.col(), 0);
4613    }
4614
4615    #[test]
4616    fn scanner_emits_first_leading_comment_before_scanning_next_comment() {
4617        let mut scanner = Scanner::new(StrInput::new("# first\n# second\nkey: value\n"));
4618
4619        assert!(matches!(
4620            scanner.next_token().unwrap().unwrap().1,
4621            TokenType::StreamStart
4622        ));
4623        assert!(matches!(
4624            scanner.next_token().unwrap().unwrap().1,
4625            TokenType::Comment(ref comment) if comment.text == " first"
4626        ));
4627        assert!(scanner.tokens.is_empty());
4628        assert!(matches!(
4629            scanner.next_token().unwrap().unwrap().1,
4630            TokenType::Comment(ref comment) if comment.text == " second"
4631        ));
4632    }
4633
4634    #[test]
4635    fn scanner_emits_quoted_scalar_comment_before_scanning_following_value() {
4636        let mut scanner = Scanner::new(StrInput::new("\"key\" # quoted\n: value\n"));
4637
4638        assert!(matches!(
4639            scanner.next_token().unwrap().unwrap().1,
4640            TokenType::StreamStart
4641        ));
4642        assert!(matches!(
4643            scanner.next_token().unwrap().unwrap().1,
4644            TokenType::Scalar(ScalarStyle::DoubleQuoted, ref value) if value == "key"
4645        ));
4646        assert!(matches!(
4647            scanner.next_token().unwrap().unwrap().1,
4648            TokenType::Comment(ref comment) if comment.text == " quoted"
4649        ));
4650    }
4651
4652    #[test]
4653    fn flow_scalar_comment_disables_adjacent_value_lookahead() {
4654        let mut scanner = Scanner::new(StrInput::new("\"key\"\n# quoted\n: value\n"));
4655
4656        scanner.fetch_flow_scalar(false).unwrap();
4657
4658        assert_eq!(scanner.adjacent_value_allowed_at, usize::MAX);
4659        assert!(matches!(
4660            scanner.tokens.front().unwrap().1,
4661            QueuedTokenType::Scalar(ScalarStyle::DoubleQuoted, ref value) if value == "key"
4662        ));
4663        assert!(scanner.tokens.iter().any(|QueuedToken(_, token)| matches!(
4664            token,
4665            QueuedTokenType::Comment(comment) if comment.text == " quoted"
4666        )));
4667    }
4668
4669    #[test]
4670    fn deferred_error_waits_for_all_comment_tokens() {
4671        let mut scanner = Scanner::new(StrInput::new("# first\n# second\n@\n"));
4672
4673        assert!(matches!(
4674            scanner.next_token().unwrap().unwrap().1,
4675            TokenType::StreamStart
4676        ));
4677        assert!(matches!(
4678            scanner.next_token().unwrap().unwrap().1,
4679            TokenType::Comment(ref comment) if comment.text == " first"
4680        ));
4681        assert!(matches!(
4682            scanner.next_token().unwrap().unwrap().1,
4683            TokenType::Comment(ref comment) if comment.text == " second"
4684        ));
4685
4686        let error = scanner.next_token().unwrap_err();
4687
4688        assert_eq!(
4689            error.kind(),
4690            &ErrorKind::UnexpectedCharacter { character: '@' }
4691        );
4692    }
4693
4694    /// Ensure anchors scanned from `StrInput` are returned as `Cow::Borrowed`.
4695    #[test]
4696    fn anchor_name_is_borrowed_for_str_input() {
4697        let mut scanner = Scanner::new(StrInput::new("&anch\n"));
4698
4699        loop {
4700            let tok = scanner
4701                .next_token()
4702                .expect("valid YAML must scan without errors")
4703                .expect("scanner must eventually produce a token");
4704            if let TokenType::Anchor(name) = tok.1 {
4705                assert!(matches!(name, Cow::Borrowed("anch")));
4706                break;
4707            }
4708        }
4709    }
4710
4711    #[test]
4712    fn anchor_name_rejects_non_printable_control_chars() {
4713        let mut scanner = Scanner::new(StrInput::new("&foo\u{0001}\n"));
4714
4715        scanner.next_token().unwrap();
4716        assert_eq!(
4717            scanner.next_token().unwrap_err().kind(),
4718            &ErrorKind::UnexpectedCharacter {
4719                character: '\u{0001}'
4720            }
4721        );
4722    }
4723
4724    #[test]
4725    fn alias_name_rejects_non_printable_control_chars() {
4726        let mut scanner = Scanner::new(StrInput::new("*foo\u{0001}\n"));
4727
4728        scanner.next_token().unwrap();
4729        assert_eq!(
4730            scanner.next_token().unwrap_err().kind(),
4731            &ErrorKind::UnexpectedCharacter {
4732                character: '\u{0001}'
4733            }
4734        );
4735    }
4736
4737    #[test]
4738    fn alias_name_is_borrowed_for_str_input() {
4739        let mut scanner = Scanner::new(StrInput::new("*anch\n"));
4740
4741        loop {
4742            let tok = scanner
4743                .next_token()
4744                .expect("valid YAML must scan without errors")
4745                .expect("scanner must eventually produce a token");
4746            if let TokenType::Alias(name) = tok.1 {
4747                assert!(matches!(name, Cow::Borrowed("anch")));
4748                break;
4749            }
4750        }
4751    }
4752
4753    #[test]
4754    fn alias_name_scans_colon_as_part_of_name() {
4755        let mut scanner = Scanner::new(StrInput::new("*foo: bar\n"));
4756
4757        loop {
4758            let tok = scanner
4759                .next_token()
4760                .expect("scanner must not fail before alias token")
4761                .expect("scanner must eventually emit an alias token");
4762
4763            if let TokenType::Alias(name) = tok.1 {
4764                assert_eq!(name.as_ref(), "foo:");
4765                break;
4766            }
4767        }
4768    }
4769
4770    #[test]
4771    fn anchor_name_scans_colon_as_part_of_name() {
4772        let mut scanner = Scanner::new(StrInput::new("&foo: bar\n"));
4773
4774        loop {
4775            let tok = scanner
4776                .next_token()
4777                .expect("scanner must not fail before anchor token")
4778                .expect("scanner must eventually emit an anchor token");
4779
4780            if let TokenType::Anchor(name) = tok.1 {
4781                assert_eq!(name.as_ref(), "foo:");
4782                break;
4783            }
4784        }
4785    }
4786
4787    /// Ensure `%TAG` directive handle and prefix are borrowed when they are verbatim (no escapes).
4788    #[test]
4789    fn tag_directive_parts_are_borrowed_for_str_input() {
4790        let mut scanner = Scanner::new(StrInput::new("%TAG !e! tag:example.com,2000:app/\n"));
4791
4792        loop {
4793            let tok = scanner
4794                .next_token()
4795                .expect("valid YAML must scan without errors")
4796                .expect("scanner must eventually produce a token");
4797            if let TokenType::TagDirective(handle, prefix) = tok.1 {
4798                assert!(matches!(handle, Cow::Borrowed("!e!")));
4799                assert!(matches!(prefix, Cow::Borrowed("tag:example.com,2000:app/")));
4800                break;
4801            }
4802        }
4803    }
4804
4805    #[test]
4806    fn tag_directive_parts_are_owned_for_buffered_input() {
4807        let mut scanner = Scanner::new(BufferedInput::new(
4808            "%TAG !e! tag:example.com,2000:app/\n".chars(),
4809        ));
4810
4811        loop {
4812            let tok = scanner
4813                .next_token()
4814                .expect("valid YAML must scan without errors")
4815                .expect("scanner must eventually produce a token");
4816            if let TokenType::TagDirective(handle, prefix) = tok.1 {
4817                assert!(matches!(handle, Cow::Owned(_)));
4818                assert_eq!(&*handle, "!e!");
4819                assert!(matches!(prefix, Cow::Owned(_)));
4820                assert_eq!(&*prefix, "tag:example.com,2000:app/");
4821                break;
4822            }
4823        }
4824    }
4825
4826    #[test]
4827    fn buffered_tag_directive_decodes_prefix_escape() {
4828        let mut scanner = Scanner::new(BufferedInput::new(
4829            "%TAG !e! %74ag:example.com,2000:app/\n".chars(),
4830        ));
4831
4832        loop {
4833            let tok = scanner
4834                .next_token()
4835                .expect("valid YAML must scan without errors")
4836                .expect("scanner must eventually produce a token");
4837            if let TokenType::TagDirective(handle, prefix) = tok.1 {
4838                assert_eq!(&*handle, "!e!");
4839                assert!(matches!(prefix, Cow::Owned(_)));
4840                assert_eq!(&*prefix, "tag:example.com,2000:app/");
4841                break;
4842            }
4843        }
4844    }
4845
4846    #[test]
4847    fn local_tag_combines_handle_text_with_escaped_suffix() {
4848        let mut scanner = Scanner::new(StrInput::new("!foo%20bar value\n"));
4849
4850        loop {
4851            let tok = scanner
4852                .next_token()
4853                .expect("valid YAML must scan without errors")
4854                .expect("scanner must eventually produce a token");
4855            if let TokenType::Tag(handle, suffix) = tok.1 {
4856                assert!(matches!(handle, Cow::Borrowed("!")));
4857                assert!(matches!(suffix, Cow::Owned(_)));
4858                assert_eq!(&*suffix, "foo bar");
4859                break;
4860            }
4861        }
4862    }
4863
4864    #[test]
4865    fn secondary_tag_requires_suffix_in_borrowed_and_buffered_paths() {
4866        assert_eq!(
4867            first_scanner_error_kind("!! value\n"),
4868            ErrorKind::MissingTagUri
4869        );
4870        assert_eq!(
4871            first_buffered_scanner_error_kind("!! value\n"),
4872            ErrorKind::MissingTagUri
4873        );
4874    }
4875
4876    #[test]
4877    fn plain_scalar_is_borrowed_when_whitespace_free_for_str_input() {
4878        let mut scanner = Scanner::new(StrInput::new("foo\n"));
4879
4880        loop {
4881            let tok = scanner
4882                .next_token()
4883                .expect("valid YAML must scan without errors")
4884                .expect("scanner must eventually produce a token");
4885            if let TokenType::Scalar(_, value) = tok.1 {
4886                assert!(matches!(value, Cow::Borrowed("foo")));
4887                break;
4888            }
4889        }
4890    }
4891
4892    #[test]
4893    fn plain_scalar_is_borrowed_when_whitespace_present_for_str_input() {
4894        let mut scanner = Scanner::new(StrInput::new("foo bar\n"));
4895
4896        loop {
4897            let tok = scanner
4898                .next_token()
4899                .expect("valid YAML must scan without errors")
4900                .expect("scanner must eventually produce a token");
4901            if let TokenType::Scalar(_, value) = tok.1 {
4902                assert!(matches!(value, Cow::Borrowed("foo bar")));
4903                break;
4904            }
4905        }
4906    }
4907
4908    #[test]
4909    fn single_quoted_scalar_is_borrowed_when_verbatim_for_str_input() {
4910        let mut scanner = Scanner::new(StrInput::new("'foo bar'\n"));
4911
4912        loop {
4913            let tok = scanner
4914                .next_token()
4915                .expect("valid YAML must scan without errors")
4916                .expect("scanner must eventually produce a token");
4917            if let TokenType::Scalar(_, value) = tok.1 {
4918                assert!(matches!(value, Cow::Borrowed("foo bar")));
4919                break;
4920            }
4921        }
4922    }
4923
4924    #[test]
4925    fn single_quoted_scalar_is_owned_when_quote_is_escaped_for_str_input() {
4926        let mut scanner = Scanner::new(StrInput::new("'foo''bar'\n"));
4927
4928        loop {
4929            let tok = scanner
4930                .next_token()
4931                .expect("valid YAML must scan without errors")
4932                .expect("scanner must eventually produce a token");
4933            if let TokenType::Scalar(_, value) = tok.1 {
4934                assert!(matches!(value, Cow::Owned(_)));
4935                assert_eq!(&*value, "foo'bar");
4936                break;
4937            }
4938        }
4939    }
4940
4941    #[test]
4942    fn double_quoted_scalar_is_borrowed_when_verbatim_for_str_input() {
4943        let mut scanner = Scanner::new(StrInput::new("\"foo bar\"\n"));
4944
4945        loop {
4946            let tok = scanner
4947                .next_token()
4948                .expect("valid YAML must scan without errors")
4949                .expect("scanner must eventually produce a token");
4950            if let TokenType::Scalar(_, value) = tok.1 {
4951                assert!(matches!(value, Cow::Borrowed("foo bar")));
4952                break;
4953            }
4954        }
4955    }
4956
4957    #[test]
4958    fn double_quoted_scalar_is_owned_when_escape_sequence_present_for_str_input() {
4959        let mut scanner = Scanner::new(StrInput::new("\"foo\\nbar\"\n"));
4960
4961        loop {
4962            let tok = scanner
4963                .next_token()
4964                .expect("valid YAML must scan without errors")
4965                .expect("scanner must eventually produce a token");
4966            if let TokenType::Scalar(_, value) = tok.1 {
4967                assert!(matches!(value, Cow::Owned(_)));
4968                assert_eq!(&*value, "foo\nbar");
4969                break;
4970            }
4971        }
4972    }
4973
4974    #[test]
4975    fn plain_key_is_borrowed_for_str_input() {
4976        // Keys are just scalars in a key position; they should also be borrowed.
4977        let mut scanner = Scanner::new(StrInput::new("mykey: value\n"));
4978
4979        let mut found_key = false;
4980        let mut key_value: Option<Cow<'_, str>> = None;
4981
4982        loop {
4983            let tok = scanner
4984                .next_token()
4985                .expect("valid YAML must scan without errors");
4986            let Some(tok) = tok else { break };
4987
4988            if matches!(tok.1, TokenType::Key) {
4989                found_key = true;
4990            } else if found_key {
4991                if let TokenType::Scalar(_, value) = tok.1 {
4992                    key_value = Some(value);
4993                    break;
4994                }
4995            }
4996        }
4997
4998        assert!(found_key, "expected to find a Key token");
4999        let key_value = key_value.expect("expected to find a scalar after Key token");
5000        assert!(
5001            matches!(key_value, Cow::Borrowed("mykey")),
5002            "key should be borrowed, got: {key_value:?}"
5003        );
5004    }
5005
5006    #[test]
5007    fn quoted_key_is_borrowed_when_verbatim_for_str_input() {
5008        let mut scanner = Scanner::new(StrInput::new("\"mykey\": value\n"));
5009
5010        let mut found_key = false;
5011        let mut key_value: Option<Cow<'_, str>> = None;
5012
5013        loop {
5014            let tok = scanner
5015                .next_token()
5016                .expect("valid YAML must scan without errors");
5017            let Some(tok) = tok else { break };
5018
5019            if matches!(tok.1, TokenType::Key) {
5020                found_key = true;
5021            } else if found_key {
5022                if let TokenType::Scalar(_, value) = tok.1 {
5023                    key_value = Some(value);
5024                    break;
5025                }
5026            }
5027        }
5028
5029        assert!(found_key, "expected to find a Key token");
5030        let key_value = key_value.expect("expected to find a scalar after Key token");
5031        assert!(
5032            matches!(key_value, Cow::Borrowed("mykey")),
5033            "quoted key should be borrowed when verbatim, got: {key_value:?}"
5034        );
5035    }
5036
5037    #[test]
5038    fn tag_handle_and_suffix_are_borrowed_for_str_input() {
5039        // Test a tag like !!str which should have handle="!!" and suffix="str"
5040        let mut scanner = Scanner::new(StrInput::new("!!str foo\n"));
5041
5042        loop {
5043            let tok = scanner
5044                .next_token()
5045                .expect("valid YAML must scan without errors")
5046                .expect("scanner must eventually produce a token");
5047            if let TokenType::Tag(handle, suffix) = tok.1 {
5048                assert!(
5049                    matches!(handle, Cow::Borrowed("!!")),
5050                    "tag handle should be borrowed, got: {handle:?}"
5051                );
5052                assert!(
5053                    matches!(suffix, Cow::Borrowed("str")),
5054                    "tag suffix should be borrowed, got: {suffix:?}"
5055                );
5056                break;
5057            }
5058        }
5059    }
5060
5061    #[test]
5062    fn local_tag_suffix_is_borrowed_for_str_input() {
5063        // Test a local tag like !mytag which should have handle="!" and suffix="mytag"
5064        let mut scanner = Scanner::new(StrInput::new("!mytag foo\n"));
5065
5066        loop {
5067            let tok = scanner
5068                .next_token()
5069                .expect("valid YAML must scan without errors")
5070                .expect("scanner must eventually produce a token");
5071            if let TokenType::Tag(handle, suffix) = tok.1 {
5072                assert!(
5073                    matches!(handle, Cow::Borrowed("!")),
5074                    "local tag handle should be '!', got: {handle:?}"
5075                );
5076                assert!(
5077                    matches!(suffix, Cow::Borrowed("mytag")),
5078                    "local tag suffix should be borrowed, got: {suffix:?}"
5079                );
5080                break;
5081            }
5082        }
5083    }
5084
5085    #[test]
5086    fn local_tag_suffix_with_punctuation_is_borrowed_for_str_input() {
5087        let mut scanner = Scanner::new(StrInput::new("!mytag/part foo\n"));
5088
5089        loop {
5090            let tok = scanner
5091                .next_token()
5092                .expect("valid YAML must scan without errors")
5093                .expect("scanner must eventually produce a token");
5094            if let TokenType::Tag(handle, suffix) = tok.1 {
5095                assert!(matches!(handle, Cow::Borrowed("!")));
5096                assert!(matches!(suffix, Cow::Borrowed("mytag/part")));
5097                break;
5098            }
5099        }
5100    }
5101
5102    #[test]
5103    fn tag_with_uri_escape_is_owned_for_str_input() {
5104        // Test a tag with URI escape like !my%20tag - suffix must be owned due to decoding
5105        let mut scanner = Scanner::new(StrInput::new("!!my%20tag foo\n"));
5106
5107        loop {
5108            let tok = scanner
5109                .next_token()
5110                .expect("valid YAML must scan without errors")
5111                .expect("scanner must eventually produce a token");
5112            if let TokenType::Tag(handle, suffix) = tok.1 {
5113                assert!(
5114                    matches!(handle, Cow::Borrowed("!!")),
5115                    "tag handle should still be borrowed, got: {handle:?}"
5116                );
5117                assert!(
5118                    matches!(suffix, Cow::Owned(_)),
5119                    "tag suffix with URI escape should be owned, got: {suffix:?}"
5120                );
5121                assert_eq!(&*suffix, "my tag");
5122                break;
5123            }
5124        }
5125    }
5126
5127    #[test]
5128    fn flow_scalar_buffer_tracks_pending_whitespace() {
5129        let mut borrowed = super::FlowScalarBuf::new_borrowed(2);
5130
5131        borrowed.note_pending_ws(5, 8);
5132        borrowed.commit_pending_ws();
5133        assert!(matches!(
5134            borrowed,
5135            super::FlowScalarBuf::Borrowed {
5136                end: 8,
5137                pending_ws_start: None,
5138                pending_ws_end: 8,
5139                ..
5140            }
5141        ));
5142
5143        borrowed.note_pending_ws(9, 11);
5144        borrowed.discard_pending_ws();
5145        assert!(matches!(
5146            borrowed,
5147            super::FlowScalarBuf::Borrowed {
5148                end: 8,
5149                pending_ws_start: None,
5150                pending_ws_end: 8,
5151                ..
5152            }
5153        ));
5154        assert!(borrowed.as_owned_mut().is_none());
5155
5156        let mut owned = super::FlowScalarBuf::new_owned();
5157        owned.as_owned_mut().unwrap().push_str("owned");
5158        assert!(matches!(owned, super::FlowScalarBuf::Owned(ref s) if s == "owned"));
5159    }
5160
5161    fn first_scanner_error_kind(input: &str) -> ErrorKind {
5162        first_scanner_error(input).kind().clone()
5163    }
5164
5165    fn first_buffered_scanner_error_kind(input: &str) -> ErrorKind {
5166        let mut scanner = Scanner::new(BufferedInput::new(input.chars()));
5167        loop {
5168            match scanner.next_token() {
5169                Ok(Some(_)) => {}
5170                Ok(None) => panic!("expected scanner error"),
5171                Err(error) => return error.kind().clone(),
5172            }
5173        }
5174    }
5175
5176    fn first_scanner_error(input: &str) -> ScanError {
5177        let mut scanner = Scanner::new(StrInput::new(input));
5178        loop {
5179            match scanner.next_token() {
5180                Ok(Some(_)) => {}
5181                Ok(None) => panic!("expected scanner error"),
5182                Err(error) => return error,
5183            }
5184        }
5185    }
5186
5187    fn first_scalar_value(input: &str) -> String {
5188        let mut scanner = Scanner::new(StrInput::new(input));
5189        loop {
5190            match scanner.next_token().expect("scanner should not error") {
5191                Some(Token(_, TokenType::Scalar(_, value))) => return value.into_owned(),
5192                Some(_) => {}
5193                None => panic!("expected scalar token"),
5194            }
5195        }
5196    }
5197
5198    fn first_buffered_scalar_value(input: &str) -> String {
5199        let mut scanner = Scanner::new(BufferedInput::new(input.chars()));
5200        loop {
5201            match scanner.next_token().expect("scanner should not error") {
5202                Some(Token(_, TokenType::Scalar(_, value))) => return value.into_owned(),
5203                Some(_) => {}
5204                None => panic!("expected scalar token"),
5205            }
5206        }
5207    }
5208
5209    #[test]
5210    fn iterator_next_emits_error_and_then_stays_empty() {
5211        let mut scanner = Scanner::new(StrInput::new("\"unterminated"));
5212
5213        let error = scanner
5214            .by_ref()
5215            .find_map(Result::err)
5216            .expect("scanner should emit the error");
5217        assert_eq!(error.kind(), &ErrorKind::UnclosedQuotedScalar);
5218        assert!(scanner.next().is_none());
5219    }
5220
5221    #[test]
5222    fn next_token_returns_none_after_stream_end() {
5223        let mut scanner = Scanner::new(StrInput::new(""));
5224
5225        while let Some(token) = scanner.next_token().unwrap() {
5226            if matches!(token.1, TokenType::StreamEnd) {
5227                break;
5228            }
5229        }
5230
5231        assert!(scanner.stream_started());
5232        assert!(scanner.stream_ended());
5233        assert!(scanner.next_token().unwrap().is_none());
5234    }
5235
5236    #[test]
5237    fn directive_name_must_be_present() {
5238        assert_eq!(
5239            first_scanner_error_kind("%\n"),
5240            ErrorKind::MissingDirectiveName
5241        );
5242    }
5243
5244    #[test]
5245    fn yaml_directive_requires_dot_between_version_numbers() {
5246        assert_eq!(
5247            first_scanner_error_kind("%YAML 1\n"),
5248            ErrorKind::MissingYamlVersionSeparator
5249        );
5250    }
5251
5252    #[test]
5253    fn yaml_directive_requires_major_version_number() {
5254        assert_eq!(
5255            first_scanner_error_kind("%YAML .2\n"),
5256            ErrorKind::MissingYamlVersion
5257        );
5258    }
5259
5260    #[test]
5261    fn yaml_directive_rejects_extremely_long_version_number() {
5262        assert_eq!(
5263            first_scanner_error_kind("%YAML 1234567890.2\n"),
5264            ErrorKind::YamlVersionTooLong
5265        );
5266    }
5267
5268    #[test]
5269    fn tag_directive_handle_must_end_with_bang() {
5270        assert_eq!(
5271            first_scanner_error_kind("%TAG !bad tag:example.com,2024:\n"),
5272            ErrorKind::ExpectedTagDirectiveBang
5273        );
5274    }
5275
5276    #[test]
5277    fn tag_directive_handle_must_start_with_bang() {
5278        assert_eq!(
5279            first_scanner_error_kind("%TAG bad! tag:example.com,2024:\n"),
5280            ErrorKind::ExpectedTagBang
5281        );
5282        assert_eq!(
5283            first_buffered_scanner_error_kind("%TAG bad! tag:example.com,2024:\n"),
5284            ErrorKind::ExpectedTagBang
5285        );
5286    }
5287
5288    #[test]
5289    fn tag_directive_prefix_must_start_with_tag_character() {
5290        assert_eq!(
5291            first_scanner_error_kind("%TAG !e! `bad\n"),
5292            ErrorKind::InvalidGlobalTagCharacter
5293        );
5294    }
5295
5296    #[test]
5297    fn tag_directive_prefix_must_end_before_invalid_content() {
5298        assert_eq!(
5299            first_scanner_error_kind("%TAG !e! tag:example.com^suffix\n"),
5300            ErrorKind::InvalidTagDirectiveTerminator
5301        );
5302    }
5303
5304    #[test]
5305    fn tag_directive_prefix_with_uri_escape_is_owned_and_decoded() {
5306        let mut scanner =
5307            Scanner::new(StrInput::new("%TAG !e! tag:example.com,2024:some%20app/\n"));
5308
5309        loop {
5310            let token = scanner
5311                .next_token()
5312                .expect("valid directive should scan")
5313                .expect("scanner must produce a directive token");
5314            if let TokenType::TagDirective(handle, prefix) = token.1 {
5315                assert!(matches!(handle, Cow::Borrowed("!e!")));
5316                assert!(matches!(prefix, Cow::Owned(_)));
5317                assert_eq!(&*prefix, "tag:example.com,2024:some app/");
5318                break;
5319            }
5320        }
5321    }
5322
5323    #[test]
5324    fn bare_bang_tag_scans_as_non_specific_tag() {
5325        let mut scanner = Scanner::new(StrInput::new("! foo\n"));
5326
5327        loop {
5328            let token = scanner
5329                .next_token()
5330                .expect("valid tag should scan")
5331                .expect("scanner must produce a tag token");
5332            if let TokenType::Tag(handle, suffix) = token.1 {
5333                assert_eq!(&*handle, "");
5334                assert_eq!(&*suffix, "!");
5335                break;
5336            }
5337        }
5338    }
5339
5340    #[test]
5341    fn tag_requires_separation_after_suffix() {
5342        assert_eq!(
5343            first_scanner_error_kind("!foo,bar\n"),
5344            ErrorKind::InvalidTagTerminator
5345        );
5346    }
5347
5348    #[test]
5349    fn verbatim_tag_requires_uri() {
5350        assert_eq!(
5351            first_scanner_error_kind("!<> foo\n"),
5352            ErrorKind::MissingTagUri
5353        );
5354    }
5355
5356    #[test]
5357    fn verbatim_tag_requires_closing_angle_bracket() {
5358        assert_eq!(
5359            first_scanner_error_kind("!<tag:yaml.org,2002:str foo\n"),
5360            ErrorKind::UnclosedVerbatimTag
5361        );
5362    }
5363
5364    #[test]
5365    fn tag_uri_escape_requires_hex_digits() {
5366        assert_eq!(
5367            first_scanner_error_kind("!!bad%zz foo\n"),
5368            ErrorKind::InvalidTagEscape
5369        );
5370    }
5371
5372    #[test]
5373    fn tag_uri_escape_rejects_bad_leading_utf8_byte() {
5374        assert_eq!(
5375            first_scanner_error_kind("!!bad%80 foo\n"),
5376            ErrorKind::InvalidTagUtf8LeadingByte
5377        );
5378    }
5379
5380    #[test]
5381    fn tag_uri_escape_rejects_bad_trailing_utf8_byte() {
5382        assert_eq!(
5383            first_scanner_error_kind("!!bad%C2%41 foo\n"),
5384            ErrorKind::InvalidTagUtf8TrailingByte
5385        );
5386    }
5387
5388    #[test]
5389    fn tag_uri_escape_rejects_invalid_utf8_codepoint() {
5390        assert_eq!(
5391            first_scanner_error_kind("!!bad%F4%90%80%80 foo\n"),
5392            ErrorKind::InvalidTagUtf8
5393        );
5394    }
5395
5396    #[test]
5397    fn anchors_and_aliases_require_names() {
5398        assert_eq!(
5399            first_scanner_error_kind("& \n"),
5400            ErrorKind::MissingAnchorOrAliasName
5401        );
5402        assert_eq!(
5403            first_scanner_error_kind("* \n"),
5404            ErrorKind::MissingAnchorOrAliasName
5405        );
5406    }
5407
5408    #[test]
5409    fn document_end_marker_rejects_trailing_content() {
5410        assert_eq!(
5411            first_scanner_error_kind("... trailing\n"),
5412            ErrorKind::InvalidDocumentEnd
5413        );
5414    }
5415
5416    #[test]
5417    fn reserved_indicators_are_rejected_outside_directives() {
5418        let error = first_scanner_error(" @\n");
5419
5420        assert_eq!(
5421            error.kind(),
5422            &ErrorKind::UnexpectedCharacter { character: '@' }
5423        );
5424    }
5425
5426    #[test]
5427    fn flow_block_entry_indicator_is_rejected() {
5428        assert_eq!(
5429            first_scanner_error_kind("[- ]\n"),
5430            ErrorKind::BlockEntryInFlowCollection
5431        );
5432    }
5433
5434    #[test]
5435    fn block_entry_after_tabbed_separator_reports_specific_error() {
5436        assert_eq!(
5437            first_scanner_error_kind("-\t- value\n"),
5438            ErrorKind::InvalidBlockEntryWhitespace
5439        );
5440    }
5441
5442    #[test]
5443    fn document_indicator_reports_unclosed_flow_collection() {
5444        let error = first_scanner_error("[\n---\n");
5445
5446        assert_eq!(
5447            error.kind(),
5448            &ErrorKind::UnclosedFlowCollection { open: '[' }
5449        );
5450    }
5451
5452    #[test]
5453    fn block_scalar_header_rejects_trailing_content() {
5454        assert_eq!(
5455            first_scanner_error_kind("|+ trailing\n"),
5456            ErrorKind::InvalidBlockScalarHeader
5457        );
5458    }
5459
5460    #[test]
5461    fn block_scalar_rejects_zero_indent_indicator() {
5462        assert_eq!(
5463            first_scanner_error_kind("|0\n"),
5464            ErrorKind::ZeroBlockScalarIndent
5465        );
5466        assert_eq!(
5467            first_scanner_error_kind("|+0\n"),
5468            ErrorKind::ZeroBlockScalarIndent
5469        );
5470    }
5471
5472    #[test]
5473    fn empty_block_scalar_at_eof_honors_chomping() {
5474        assert_eq!(first_scalar_value("|\n"), "");
5475        assert_eq!(first_scalar_value("|-\n"), "");
5476        assert_eq!(first_scalar_value("|+\n"), "");
5477        assert_eq!(first_scalar_value("|+\n\n"), "\n");
5478        assert_eq!(first_scalar_value("|+\n   "), "\n");
5479    }
5480
5481    #[test]
5482    fn buffered_block_scalar_reads_content_past_lookahead_window() {
5483        assert_eq!(
5484            first_buffered_scalar_value("|\n  abcdefghijklmnopqrstuvwxyz\n"),
5485            "abcdefghijklmnopqrstuvwxyz\n"
5486        );
5487    }
5488
5489    #[test]
5490    fn explicit_indent_block_scalar_can_end_at_document_marker() {
5491        assert_eq!(first_scalar_value("|1\n...\n"), "");
5492    }
5493
5494    #[test]
5495    fn root_explicit_indent_block_scalar_rejects_underindented_content() {
5496        assert_eq!(
5497            first_scanner_error_kind("|2\nx\n"),
5498            ErrorKind::InvalidBlockScalarIndent
5499        );
5500    }
5501
5502    #[test]
5503    fn quoted_scalar_rejects_document_indicator_at_line_start() {
5504        assert_eq!(
5505            first_scanner_error_kind("\"one\n---\ntwo\"\n"),
5506            ErrorKind::DocumentIndicatorInQuotedScalar
5507        );
5508    }
5509
5510    #[test]
5511    fn quoted_scalar_rejects_tab_indentation_after_line_break() {
5512        assert_eq!(
5513            first_scanner_error_kind("a: \"one\n\tbad\"\n"),
5514            ErrorKind::TabInIndentation
5515        );
5516    }
5517
5518    #[test]
5519    fn quoted_scalar_rejects_underindented_continuation() {
5520        assert_eq!(
5521            first_scanner_error_kind("a: \"one\nbad\"\n"),
5522            ErrorKind::InvalidQuotedScalarIndent
5523        );
5524    }
5525
5526    #[test]
5527    fn quoted_scalar_trailing_content_error_names_quote_style() {
5528        assert_eq!(
5529            first_scanner_error_kind("'foo' trailing\n"),
5530            ErrorKind::InvalidTrailingSingleQuotedScalar
5531        );
5532        assert_eq!(
5533            first_scanner_error_kind("\"foo\" trailing\n"),
5534            ErrorKind::InvalidTrailingDoubleQuotedScalar
5535        );
5536    }
5537
5538    #[test]
5539    fn quoted_scalar_escape_errors_cover_hex_and_surrogate_edges() {
5540        assert_eq!(
5541            first_scanner_error_kind("\"\\xG0\"\n"),
5542            ErrorKind::InvalidQuotedScalarHexEscape
5543        );
5544        assert_eq!(
5545            first_scanner_error_kind("\"\\uD800\\uGGGG\"\n"),
5546            ErrorKind::InvalidLowSurrogateHexEscape
5547        );
5548        assert_eq!(
5549            first_scanner_error_kind("\"\\uD800\\u0041\"\n"),
5550            ErrorKind::InvalidLowSurrogate
5551        );
5552        assert_eq!(
5553            first_scanner_error_kind("\"\\U00110000\"\n"),
5554            ErrorKind::InvalidUnicodeEscape
5555        );
5556    }
5557
5558    #[test]
5559    fn indented_flow_scalar_reports_invalid_indentation() {
5560        assert_eq!(
5561            first_scanner_error_kind("a:\n  [\nfoo]\n"),
5562            ErrorKind::InvalidIndentation
5563        );
5564    }
5565
5566    #[test]
5567    fn required_simple_key_requires_value_at_stream_end() {
5568        let error = first_scanner_error("a:\n&b\n- c\n");
5569
5570        assert_eq!(error.kind(), &ErrorKind::SimpleKeyExpected);
5571        assert_eq!(error.marker().index(), 3);
5572        assert_eq!(error.marker().line(), 2);
5573        assert_eq!(error.marker().col(), 0);
5574    }
5575
5576    #[test]
5577    fn plain_scalar_rejects_dash_before_flow_indicator() {
5578        assert_eq!(
5579            first_scanner_error_kind("[-]\n"),
5580            ErrorKind::PlainScalarStartsWithDashFlowIndicator
5581        );
5582    }
5583
5584    #[test]
5585    fn explicit_key_rejects_tab_after_indicator() {
5586        assert_eq!(
5587            first_scanner_error_kind("? \tfoo\n"),
5588            ErrorKind::TabNotAllowed
5589        );
5590    }
5591
5592    #[test]
5593    fn flow_mapping_rejects_adjacent_collection_value_after_plain_key() {
5594        assert_eq!(
5595            first_scanner_error_kind("[a:[]]\n"),
5596            ErrorKind::FlowMappingValueAdjacentCollection
5597        );
5598    }
5599
5600    #[test]
5601    fn implicit_flow_mapping_colon_cannot_move_to_next_line() {
5602        assert_eq!(
5603            first_scanner_error_kind("[foo\n: bar]\n"),
5604            ErrorKind::InvalidColonPlacement
5605        );
5606    }
5607
5608    #[test]
5609    fn invalid_simple_key_token_positions_are_scan_errors() {
5610        for (tokens_parsed, token_number) in [(1, 0), (0, 1)] {
5611            let mut scanner = Scanner::new(StrInput::new(": value\n"));
5612            scanner.fetch_stream_start();
5613            scanner.tokens.clear();
5614            scanner.tokens_parsed = tokens_parsed;
5615
5616            let simple_key = scanner
5617                .simple_keys
5618                .last_mut()
5619                .expect("stream start should create a simple key slot");
5620            simple_key.possible = true;
5621            simple_key.token_number = token_number;
5622
5623            let error = scanner
5624                .fetch_value()
5625                .expect_err("invalid simple key position should be reported as a scan error");
5626            assert_eq!(error.kind(), &ErrorKind::InvalidSimpleKey);
5627            assert_eq!(error.marker(), &Marker::new(0, 1, 0));
5628        }
5629    }
5630
5631    #[test]
5632    fn issue14_alias_scanner_consumes_colon_as_name_character() {
5633        let mut scanner = Scanner::new(StrInput::new("*foo: bar\n"));
5634
5635        assert!(matches!(
5636            scanner.next_token().unwrap().unwrap().1,
5637            TokenType::StreamStart
5638        ));
5639
5640        let token = scanner.next_token().unwrap().unwrap();
5641
5642        assert!(
5643            matches!(token.1, TokenType::Alias(ref name) if name.as_ref() == "foo:"),
5644            "expected `*foo: bar` to start with Alias(\"foo:\"), got {token:?}"
5645        );
5646    }
5647
5648    #[test]
5649    fn issue14_anchor_scanner_consumes_colon_as_name_character() {
5650        let mut scanner = Scanner::new(StrInput::new("&foo: bar\n"));
5651
5652        assert!(matches!(
5653            scanner.next_token().unwrap().unwrap().1,
5654            TokenType::StreamStart
5655        ));
5656
5657        let token = scanner.next_token().unwrap().unwrap();
5658
5659        assert!(
5660            matches!(token.1, TokenType::Anchor(ref name) if name.as_ref() == "foo:"),
5661            "expected `&foo: bar` to start with Anchor(\"foo:\"), got {token:?}"
5662        );
5663    }
5664}