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