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, find_non_printable, is_anchor_char, is_blank_or_breakz, is_bom, is_break,
23        is_breakz, is_flow, is_hex, 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) = find_non_printable(&string) {
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) = find_non_printable(scalar_text) {
3806            return Err(ScanError::from_kind(
3807                start_mark,
3808                ErrorKind::UnexpectedCharacter { character },
3809            ));
3810        }
3811        self.ensure_current_char_is_printable()?;
3812
3813        if has_content {
3814            let contents = if let Some(slice) = borrowed_contents {
3815                Cow::Borrowed(slice)
3816            } else {
3817                Cow::Owned(string.expect("owned plain scalar has an output buffer"))
3818            };
3819
3820            Ok(Token(
3821                Span::new(start_mark, end_mark),
3822                TokenType::Scalar(ScalarStyle::Plain, contents),
3823            ))
3824        } else {
3825            // `fetch_plain_scalar` must absolutely consume at least one byte. Otherwise,
3826            // `fetch_next_token` will never stop calling it. An empty plain scalar may happen with
3827            // erroneous inputs such as "{...".
3828            Err(ScanError::from_kind(
3829                start_mark,
3830                ErrorKind::UnexpectedEndOfPlainScalar,
3831            ))
3832        }
3833    }
3834
3835    fn fetch_key(&mut self) -> ScanResult {
3836        let start_mark = self.mark;
3837        if self.flow_level == 0 {
3838            // Check if we are allowed to start a new key (not necessarily simple).
3839            if !self.simple_key_allowed {
3840                return Err(self.scan_error(ErrorKind::MappingKeyNotAllowed));
3841            }
3842            self.roll_indent(
3843                start_mark.col,
3844                None,
3845                TokenType::BlockMappingStart,
3846                start_mark,
3847            );
3848        } else {
3849            // The scanner, upon emitting a `Key`, will prepend a `MappingStart` event.
3850            self.set_current_flow_mapping_started(true);
3851        }
3852
3853        self.remove_simple_key()?;
3854
3855        if self.flow_level == 0 {
3856            self.allow_simple_key();
3857        } else {
3858            self.disallow_simple_key();
3859        }
3860
3861        self.skip_non_blank();
3862        let end_mark = self.mark;
3863        let token_index = self.tokens.len();
3864        self.explicit_key_tab_check_pending = false;
3865        let stopped_after_comment = self.skip_yaml_whitespace(true)?;
3866        if self.input.peek() == '\t' {
3867            return Err(self.scan_error(ErrorKind::TabNotAllowed));
3868        }
3869        self.explicit_key_tab_check_pending = stopped_after_comment;
3870        self.insert_token(
3871            token_index,
3872            Token(Span::new(start_mark, end_mark), TokenType::Key),
3873        );
3874        Ok(())
3875    }
3876
3877    /// Fetch a value in a mapping inside of a flow collection.
3878    ///
3879    /// This must not be called if [`self.flow_level`] is 0. This ensures the rules surrounding
3880    /// values in flow collections are respected prior to calling [`fetch_value`].
3881    ///
3882    /// [`self.flow_level`]: Self::flow_level
3883    /// [`fetch_value`]: Self::fetch_value
3884    fn fetch_flow_value(&mut self) -> ScanResult {
3885        let nc = self.input.peek_nth(1);
3886
3887        // If we encounter a ':' inside a flow collection and it is not immediately
3888        // followed by a blank or breakz:
3889        //   - We must check whether an adjacent value is allowed
3890        //     `["a":[]]` is valid. If the key is double-quoted, no need for a space. This
3891        //     is needed for JSON compatibility.
3892        //   - If not, we must ensure there is a space after the ':' and before its value.
3893        //     `[a: []]` is valid while `[a:[]]` isn't. `[a:b]` is treated as `["a:b"]`.
3894        //   - But if the value is empty (null), then it's okay.
3895        // The last line is for YAMLs like `[a:]`. The ':' is followed by a ']' (which is a
3896        // flow character), but the ']' is not the value. The value is an invisible empty
3897        // space which is represented as null ('~').
3898        if self.mark.index() != self.adjacent_value_allowed_at && (nc == '[' || nc == '{') {
3899            return Err(self.scan_error(ErrorKind::FlowMappingValueAdjacentCollection));
3900        }
3901
3902        self.fetch_value()
3903    }
3904
3905    /// Fetch a value from a mapping (after a `:`).
3906    fn fetch_value(&mut self) -> ScanResult {
3907        let sk = *self.simple_keys.last().unwrap();
3908        let start_mark = self.mark;
3909        let is_implicit_flow_mapping = self.current_flow_collection_is_sequence()
3910            && !self.current_flow_mapping_started()
3911            && !self.implicit_flow_mapping_states.is_empty();
3912        if is_implicit_flow_mapping {
3913            *self.implicit_flow_mapping_states.last_mut().unwrap() =
3914                ImplicitMappingState::Inside(self.flow_level);
3915        }
3916
3917        // Skip over ':'.
3918        self.skip_non_blank();
3919        // Error detection: if ':' is followed by tab(s) without any space, and then what looks
3920        // like a value, emit a helpful error. The check for '-' or alphanumeric is an intentional
3921        // heuristic that catches common cases (e.g., `key:\tvalue`, `key:\t-item`) without
3922        // rejecting valid YAML like `key:\t|` (block scalar) or `key:\t"quoted"`.
3923        // Note: This heuristic won't catch Unicode value starters like `key:\täöü`, but such
3924        // cases will still fail to parse correctly (just with a less specific error message).
3925        let mut trailing_tokens = VecDeque::new();
3926        if self.input.look_ch() == '\t' {
3927            let trailing_token_index = self.tokens.len();
3928            let whitespace = self.skip_ws_to_eol(SkipTabs::Yes)?;
3929            trailing_tokens = self.tokens.split_off(trailing_token_index);
3930
3931            if !whitespace.has_valid_yaml_ws()
3932                && (self.input.peek() == '-' || self.input.next_is_alpha())
3933            {
3934                return Err(self.scan_error(ErrorKind::InvalidMappingValueWhitespace));
3935            }
3936        }
3937
3938        if sk.possible {
3939            let token_index = self.simple_key_token_index(&sk, start_mark)?;
3940            // insert simple key
3941            let tok = Token(Span::empty(sk.mark), TokenType::Key);
3942            self.insert_token(token_index, tok);
3943            if is_implicit_flow_mapping {
3944                if sk.mark.line < start_mark.line {
3945                    return Err(ScanError::from_kind(
3946                        start_mark,
3947                        ErrorKind::InvalidColonPlacement,
3948                    ));
3949                }
3950                self.insert_token(
3951                    token_index,
3952                    Token(Span::empty(sk.mark), TokenType::FlowMappingStart),
3953                );
3954            }
3955
3956            // Add the BLOCK-MAPPING-START token if needed.
3957            self.roll_indent(
3958                sk.mark.col,
3959                Some(sk.token_number),
3960                TokenType::BlockMappingStart,
3961                sk.mark,
3962            );
3963            self.roll_one_col_indent();
3964
3965            self.simple_keys.last_mut().unwrap().possible = false;
3966            self.disallow_simple_key();
3967        } else {
3968            if is_implicit_flow_mapping {
3969                self.tokens
3970                    .push_back(Token(Span::empty(start_mark), TokenType::FlowMappingStart).into());
3971            }
3972            // The ':' indicator follows a complex key.
3973            if self.flow_level == 0 {
3974                if !self.simple_key_allowed {
3975                    return Err(ScanError::from_kind(
3976                        start_mark,
3977                        ErrorKind::MappingValueNotAllowed,
3978                    ));
3979                }
3980
3981                self.roll_indent(
3982                    start_mark.col,
3983                    None,
3984                    TokenType::BlockMappingStart,
3985                    start_mark,
3986                );
3987            }
3988            self.roll_one_col_indent();
3989
3990            if self.flow_level == 0 {
3991                self.allow_simple_key();
3992            } else {
3993                self.disallow_simple_key();
3994            }
3995        }
3996        self.tokens
3997            .push_back(Token(Span::empty(start_mark), TokenType::Value).into());
3998        self.tokens.append(&mut trailing_tokens);
3999
4000        Ok(())
4001    }
4002
4003    /// Add an indentation level to the stack with the given block token, if needed.
4004    ///
4005    /// An indentation level is added only if:
4006    ///   - We are not in a flow-style construct (which don't have indentation per-se).
4007    ///   - The current column is further indented than the last indent we have registered.
4008    fn roll_indent(
4009        &mut self,
4010        col: usize,
4011        number: Option<usize>,
4012        tok: TokenType<'input>,
4013        mark: Marker,
4014    ) {
4015        if self.flow_level > 0 {
4016            return;
4017        }
4018
4019        // If the last indent was a non-block indent, remove it.
4020        // This means that we prepared an indent that we thought we wouldn't use, but realized just
4021        // now that it is a block indent.
4022        if self.indent <= col as isize {
4023            if let Some(indent) = self.indents.last() {
4024                if !indent.needs_block_end {
4025                    self.indent = indent.indent;
4026                    self.indents.pop();
4027                }
4028            }
4029        }
4030
4031        if self.indent < col as isize {
4032            self.indents.push(Indent {
4033                indent: self.indent,
4034                needs_block_end: true,
4035            });
4036            self.indent = col as isize;
4037            let tokens_parsed = self.tokens_parsed;
4038            match number {
4039                Some(n) => self.insert_token(n - tokens_parsed, Token(Span::empty(mark), tok)),
4040                None => self.tokens.push_back(Token(Span::empty(mark), tok).into()),
4041            }
4042        }
4043    }
4044
4045    /// Pop indentation levels from the stack as much as needed.
4046    ///
4047    /// Indentation levels are popped from the stack while they are further indented than `col`.
4048    /// If we are in a flow-style construct (which don't have indentation per-se), this function
4049    /// does nothing.
4050    fn unroll_indent(&mut self, col: isize) {
4051        if self.flow_level > 0 {
4052            return;
4053        }
4054        while self.indent > col {
4055            let indent = self.indents.pop().unwrap();
4056            self.indent = indent.indent;
4057            if indent.needs_block_end {
4058                self.tokens
4059                    .push_back(Token(Span::empty(self.mark), TokenType::BlockEnd).into());
4060            }
4061        }
4062    }
4063
4064    /// Add an indentation level of 1 column that does not start a block.
4065    ///
4066    /// See the documentation of [`Indent::needs_block_end`] for more details.
4067    /// An indentation is not added if we are inside a flow level or if the last indent is already
4068    /// a non-block indent.
4069    fn roll_one_col_indent(&mut self) {
4070        if self.flow_level == 0 && self.indents.last().is_some_and(|x| x.needs_block_end) {
4071            self.indents.push(Indent {
4072                indent: self.indent,
4073                needs_block_end: false,
4074            });
4075            self.indent += 1;
4076        }
4077    }
4078
4079    /// Unroll all last indents created with [`Self::roll_one_col_indent`].
4080    fn unroll_non_block_indents(&mut self) {
4081        while let Some(indent) = self.indents.last() {
4082            if indent.needs_block_end {
4083                break;
4084            }
4085            self.indent = indent.indent;
4086            self.indents.pop();
4087        }
4088    }
4089
4090    /// Mark the next token to be inserted as a potential simple key.
4091    fn save_simple_key(&mut self) {
4092        if self.simple_key_allowed {
4093            let required = self.flow_level == 0
4094                && self.indent == (self.mark.col as isize)
4095                && self.indents.last().unwrap().needs_block_end;
4096
4097            if let Some(last) = self.simple_keys.last_mut() {
4098                *last = SimpleKey {
4099                    mark: self.mark,
4100                    possible: true,
4101                    required,
4102                    token_number: self.tokens_parsed + self.tokens.len(),
4103                };
4104            }
4105        }
4106    }
4107
4108    fn remove_simple_key(&mut self) -> ScanResult {
4109        let last = self.simple_keys.last_mut().unwrap();
4110        if last.possible && last.required {
4111            return Err(Self::simple_key_expected(last.mark));
4112        }
4113
4114        last.possible = false;
4115        Ok(())
4116    }
4117
4118    /// Return whether the scanner is inside a block but outside of a flow sequence.
4119    fn is_within_block(&self) -> bool {
4120        !self.indents.is_empty()
4121    }
4122
4123    /// If an implicit mapping had started, end it.
4124    ///
4125    /// This function does not pop the state in [`implicit_flow_mapping_states`].
4126    ///
4127    /// [`implicit_flow_mapping_states`]: Self::implicit_flow_mapping_states
4128    fn end_implicit_mapping(&mut self, mark: Marker, flow_level: u8) {
4129        if self
4130            .implicit_flow_mapping_states
4131            .last()
4132            .is_some_and(|state| *state == ImplicitMappingState::Inside(flow_level))
4133        {
4134            *self.implicit_flow_mapping_states.last_mut().unwrap() = ImplicitMappingState::Possible;
4135            self.set_current_flow_mapping_started(false);
4136            self.tokens
4137                .push_back(Token(Span::empty(mark), TokenType::FlowMappingEnd).into());
4138        }
4139    }
4140
4141    fn current_flow_collection_is_sequence(&self) -> bool {
4142        self.flow_markers
4143            .last()
4144            .is_some_and(|(_, bracket)| *bracket == '[')
4145    }
4146
4147    fn current_flow_mapping_started(&self) -> bool {
4148        self.flow_mapping_started.last().copied().unwrap_or(false)
4149    }
4150
4151    fn set_current_flow_mapping_started(&mut self, started: bool) {
4152        if let Some(current) = self.flow_mapping_started.last_mut() {
4153            *current = started;
4154        }
4155    }
4156}
4157
4158/// Chomping, how final line breaks and trailing empty lines are interpreted.
4159///
4160/// See YAML spec 8.1.1.2.
4161#[derive(PartialEq, Eq)]
4162enum Chomping {
4163    /// The final line break and any trailing empty lines are excluded.
4164    Strip,
4165    /// The final line break is preserved, but trailing empty lines are excluded.
4166    Clip,
4167    /// The final line break and trailing empty lines are included.
4168    Keep,
4169}
4170
4171#[cfg(test)]
4172mod test {
4173    use alloc::{
4174        borrow::{Cow, ToOwned},
4175        rc::Rc,
4176        string::String,
4177        vec,
4178        vec::Vec,
4179    };
4180    use core::cell::Cell;
4181
4182    use crate::error::{ErrorKind, ScanError};
4183    use crate::{
4184        input::{str::StrInput, BorrowedInput, BufferedInput, Input},
4185        scanner::{
4186            Comment, Marker, Placement, QueuedToken, QueuedTokenType, ScalarStyle, Scanner, Span,
4187            Token, TokenType,
4188        },
4189    };
4190
4191    struct CountingChars {
4192        chars: alloc::vec::IntoIter<char>,
4193        read: Rc<Cell<usize>>,
4194    }
4195
4196    impl Iterator for CountingChars {
4197        type Item = char;
4198
4199        fn next(&mut self) -> Option<Self::Item> {
4200            let next = self.chars.next();
4201            if next.is_some() {
4202                self.read.set(self.read.get() + 1);
4203            }
4204            next
4205        }
4206    }
4207
4208    struct SlicingOnlyInput<'input> {
4209        inner: StrInput<'input>,
4210        expose_slice: bool,
4211    }
4212
4213    impl<'input> SlicingOnlyInput<'input> {
4214        fn new(source: &'input str, expose_slice: bool) -> Self {
4215            Self {
4216                inner: StrInput::new(source),
4217                expose_slice,
4218            }
4219        }
4220    }
4221
4222    impl Input for SlicingOnlyInput<'_> {
4223        fn lookahead(&mut self, count: usize) {
4224            self.inner.lookahead(count);
4225        }
4226
4227        fn buflen(&self) -> usize {
4228            self.inner.buflen()
4229        }
4230
4231        fn bufmaxlen(&self) -> usize {
4232            self.inner.bufmaxlen()
4233        }
4234
4235        fn raw_read_ch(&mut self) -> char {
4236            self.inner.raw_read_ch()
4237        }
4238
4239        fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
4240            self.inner.raw_read_non_breakz_ch()
4241        }
4242
4243        fn skip(&mut self) {
4244            self.inner.skip();
4245        }
4246
4247        fn skip_n(&mut self, count: usize) {
4248            self.inner.skip_n(count);
4249        }
4250
4251        fn peek(&self) -> char {
4252            self.inner.peek()
4253        }
4254
4255        fn peek_nth(&self, n: usize) -> char {
4256            self.inner.peek_nth(n)
4257        }
4258
4259        fn byte_offset(&self) -> Option<usize> {
4260            self.inner.byte_offset()
4261        }
4262
4263        fn slice_bytes(&self, start: usize, end: usize) -> Option<&str> {
4264            if self.expose_slice {
4265                self.inner.slice_bytes(start, end)
4266            } else {
4267                None
4268            }
4269        }
4270    }
4271
4272    impl<'input> BorrowedInput<'input> for SlicingOnlyInput<'input> {
4273        fn slice_borrowed(&self, _start: usize, _end: usize) -> Option<&'input str> {
4274            None
4275        }
4276    }
4277
4278    struct SmallReportedBufferInput<'input> {
4279        inner: StrInput<'input>,
4280        reported_bufmaxlen: usize,
4281    }
4282
4283    impl<'input> SmallReportedBufferInput<'input> {
4284        fn new(source: &'input str, reported_bufmaxlen: usize) -> Self {
4285            Self {
4286                inner: StrInput::new(source),
4287                reported_bufmaxlen,
4288            }
4289        }
4290    }
4291
4292    impl Input for SmallReportedBufferInput<'_> {
4293        fn lookahead(&mut self, count: usize) {
4294            self.inner.lookahead(count);
4295        }
4296
4297        fn buflen(&self) -> usize {
4298            self.inner.buflen()
4299        }
4300
4301        fn bufmaxlen(&self) -> usize {
4302            self.reported_bufmaxlen
4303        }
4304
4305        fn raw_read_ch(&mut self) -> char {
4306            self.inner.raw_read_ch()
4307        }
4308
4309        fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
4310            self.inner.raw_read_non_breakz_ch()
4311        }
4312
4313        fn skip(&mut self) {
4314            self.inner.skip();
4315        }
4316
4317        fn skip_n(&mut self, count: usize) {
4318            self.inner.skip_n(count);
4319        }
4320
4321        fn peek(&self) -> char {
4322            self.inner.peek()
4323        }
4324
4325        fn peek_nth(&self, n: usize) -> char {
4326            self.inner.peek_nth(n)
4327        }
4328    }
4329
4330    impl<'input> BorrowedInput<'input> for SmallReportedBufferInput<'input> {
4331        fn slice_borrowed(&self, start: usize, end: usize) -> Option<&'input str> {
4332            self.inner.slice_borrowed(start, end)
4333        }
4334    }
4335
4336    #[test]
4337    fn anchor_character_set_allows_colon_and_rejects_flow_indicators() {
4338        use super::is_anchor_char;
4339
4340        assert!(is_anchor_char('x'));
4341        assert!(is_anchor_char('-'));
4342        assert!(is_anchor_char('_'));
4343        assert!(is_anchor_char(':'));
4344        assert!(is_anchor_char('#'));
4345        assert!(is_anchor_char('/'));
4346        assert!(is_anchor_char('?'));
4347
4348        for c in [',', '[', ']', '{', '}', ' ', '\t', '\n', '\r', '\0'] {
4349            assert!(
4350                !is_anchor_char(c),
4351                "character {c:?} must not be accepted in anchor/alias names"
4352            );
4353        }
4354    }
4355
4356    #[test]
4357    fn flow_simple_key_length_limit_bounds_buffering() {
4358        let mut yaml = String::from("[\n\"start\"\n");
4359        for _ in 0..600 {
4360            yaml.push_str("\"x\"\n");
4361        }
4362        let total_chars = yaml.chars().count();
4363        let read = Rc::new(Cell::new(0));
4364        let chars = yaml.chars().collect::<Vec<_>>().into_iter();
4365        let mut scanner = Scanner::new(BufferedInput::new(CountingChars {
4366            chars,
4367            read: Rc::clone(&read),
4368        }));
4369
4370        assert!(matches!(
4371            scanner.next_token().unwrap().unwrap().1,
4372            TokenType::StreamStart
4373        ));
4374
4375        let token = scanner.next_token().unwrap().unwrap();
4376        assert!(matches!(token.1, TokenType::FlowSequenceStart));
4377
4378        let token = scanner.next_token().unwrap().unwrap();
4379        assert!(matches!(
4380            token.1,
4381            TokenType::Scalar(_, ref value) if value == "start"
4382        ));
4383        assert!(
4384            read.get() < total_chars,
4385            "scanner consumed all {total_chars} chars before yielding the first flow scalar"
4386        );
4387        assert!(
4388            read.get() <= super::SIMPLE_KEY_MAX_LOOKAHEAD + 128,
4389            "scanner read {} chars before yielding the first flow scalar",
4390            read.get()
4391        );
4392    }
4393
4394    #[test]
4395    fn block_scalar_indent_tolerates_small_reported_bufmaxlen() {
4396        let mut scanner = Scanner::new(SmallReportedBufferInput::new("|\n  value\n", 0));
4397
4398        let scalar = scanner
4399            .find_map(
4400                |token| match token.expect("valid YAML should scan without errors") {
4401                    Token(_, TokenType::Scalar(ScalarStyle::Literal, value)) => {
4402                        Some(value.into_owned())
4403                    }
4404                    _ => None,
4405                },
4406            )
4407            .expect("expected block scalar token");
4408
4409        assert_eq!(scalar, "value\n");
4410    }
4411
4412    #[test]
4413    fn plain_scalar_chunk_tolerates_small_reported_bufmaxlen() {
4414        let mut scanner = Scanner::new(SmallReportedBufferInput::new("plain\n", 0));
4415
4416        let scalar = scanner
4417            .find_map(
4418                |token| match token.expect("valid YAML should scan without errors") {
4419                    Token(_, TokenType::Scalar(ScalarStyle::Plain, value)) => {
4420                        Some(value.into_owned())
4421                    }
4422                    _ => None,
4423                },
4424            )
4425            .expect("expected plain scalar token");
4426
4427        assert_eq!(scalar, "plain");
4428    }
4429
4430    fn first_token_slice(
4431        yaml: &str,
4432        matches_token: impl Fn(&TokenType<'_>) -> bool,
4433    ) -> Option<String> {
4434        let mut scanner = Scanner::new(StrInput::new(yaml));
4435
4436        loop {
4437            let token = scanner
4438                .next_token()
4439                .expect("scanner should accept the test YAML")?;
4440            if matches_token(&token.1) {
4441                return token.0.slice(yaml).map(ToOwned::to_owned);
4442            }
4443        }
4444    }
4445
4446    #[test]
4447    fn flow_indicator_token_spans_cover_only_the_indicator() {
4448        assert_eq!(
4449            first_token_slice("[ # c\n  a]\n", |token| matches!(
4450                token,
4451                TokenType::FlowSequenceStart
4452            ))
4453            .as_deref(),
4454            Some("[")
4455        );
4456        assert_eq!(
4457            first_token_slice("{ # c\n  a: b}\n", |token| matches!(
4458                token,
4459                TokenType::FlowMappingStart
4460            ))
4461            .as_deref(),
4462            Some("{")
4463        );
4464        assert_eq!(
4465            first_token_slice("[a] # c\n", |token| matches!(
4466                token,
4467                TokenType::FlowSequenceEnd
4468            ))
4469            .as_deref(),
4470            Some("]")
4471        );
4472        assert_eq!(
4473            first_token_slice("{a: b} # c\n", |token| matches!(
4474                token,
4475                TokenType::FlowMappingEnd
4476            ))
4477            .as_deref(),
4478            Some("}")
4479        );
4480        assert_eq!(
4481            first_token_slice("[a, # c\nb]\n", |token| matches!(
4482                token,
4483                TokenType::FlowEntry
4484            ))
4485            .as_deref(),
4486            Some(",")
4487        );
4488    }
4489
4490    #[test]
4491    fn explicit_key_token_span_covers_only_the_indicator() {
4492        assert_eq!(
4493            first_token_slice("? # c\n: value\n", |token| matches!(token, TokenType::Key))
4494                .as_deref(),
4495            Some("?")
4496        );
4497    }
4498
4499    #[test]
4500    fn comment_capture_does_not_change_leading_whitespace() {
4501        let mut scanner = Scanner::new(StrInput::new("# comment\n"));
4502
4503        let token = scanner.scan_comment_token().unwrap();
4504
4505        assert!(scanner.leading_whitespace);
4506        assert!(matches!(token.1, TokenType::Comment(ref comment) if comment.text == " comment"));
4507
4508        let mut scanner = Scanner::new(BufferedInput::new("# streaming\n".chars()));
4509        scanner.input.lookahead(1);
4510
4511        let token = scanner.scan_comment_token().unwrap();
4512
4513        assert!(scanner.leading_whitespace);
4514        assert!(matches!(token.1, TokenType::Comment(ref comment) if comment.text == " streaming"));
4515    }
4516
4517    #[test]
4518    fn comment_capture_falls_back_to_owned_slice_when_borrow_unavailable() {
4519        let mut scanner = Scanner::new(SlicingOnlyInput::new("# sliced\n", true));
4520        scanner.input.lookahead(2);
4521        assert_eq!(scanner.input.peek_nth(1), ' ');
4522
4523        let token = scanner.scan_comment_token().unwrap();
4524
4525        assert!(matches!(token.1, TokenType::Comment(ref comment)
4526            if matches!(comment.text, Cow::Owned(ref text) if text == " sliced")));
4527    }
4528
4529    #[test]
4530    fn comment_capture_errors_when_offsets_have_no_slice() {
4531        let mut scanner = Scanner::new(SlicingOnlyInput::new("# broken\n", false));
4532
4533        let error = scanner.scan_comment_token().unwrap_err();
4534
4535        assert_eq!(error.kind(), &ErrorKind::InputOffsetsWithoutSlice);
4536    }
4537
4538    #[test]
4539    fn queued_token_roundtrips_public_token_variants() {
4540        let span = Span::new(Marker::new(0, 1, 0), Marker::new(7, 1, 7));
4541        let tokens = [
4542            Token(span, TokenType::StreamStart),
4543            Token(span, TokenType::StreamEnd),
4544            Token(span, TokenType::VersionDirective(1, 2)),
4545            Token(
4546                span,
4547                TokenType::TagDirective(Cow::Borrowed("!app!"), Cow::Borrowed("tag:app.example,")),
4548            ),
4549            Token(span, TokenType::DocumentStart),
4550            Token(span, TokenType::DocumentEnd),
4551            Token(span, TokenType::BlockSequenceStart),
4552            Token(span, TokenType::BlockMappingStart),
4553            Token(span, TokenType::BlockEnd),
4554            Token(span, TokenType::FlowSequenceStart),
4555            Token(span, TokenType::FlowSequenceEnd),
4556            Token(span, TokenType::FlowMappingStart),
4557            Token(span, TokenType::FlowMappingEnd),
4558            Token(span, TokenType::BlockEntry),
4559            Token(span, TokenType::FlowEntry),
4560            Token(span, TokenType::Key),
4561            Token(span, TokenType::Value),
4562            Token(span, TokenType::Alias(Cow::Borrowed("alias"))),
4563            Token(span, TokenType::Anchor(Cow::Borrowed("anchor"))),
4564            Token(
4565                span,
4566                TokenType::Tag(Cow::Borrowed("!"), Cow::Borrowed("tag")),
4567            ),
4568            Token(
4569                span,
4570                TokenType::Scalar(ScalarStyle::Literal, Cow::Borrowed("scalar")),
4571            ),
4572            Token(
4573                span,
4574                TokenType::Comment(
4575                    Comment::new(Cow::Borrowed(" comment")).with_placement(Placement::Right),
4576                ),
4577            ),
4578            Token(
4579                span,
4580                TokenType::ReservedDirective(
4581                    "reserved".to_owned(),
4582                    vec!["one".to_owned(), "two".to_owned()],
4583                ),
4584            ),
4585        ];
4586
4587        for token in tokens {
4588            let queued: QueuedToken = token.clone().into();
4589
4590            assert_eq!(queued.into_public(), token);
4591        }
4592    }
4593
4594    #[test]
4595    fn comment_skipping_path_consumes_comment_without_tokenizing_it() {
4596        let mut scanner = Scanner::new(StrInput::new("# skipped\nnext: value\n"));
4597
4598        scanner.skip_yaml_whitespace(false).unwrap();
4599
4600        assert!(scanner.tokens.is_empty());
4601        assert_eq!(scanner.mark.line(), 2);
4602        assert_eq!(scanner.mark.col(), 0);
4603    }
4604
4605    #[test]
4606    fn yaml_whitespace_can_stop_after_queued_comment() {
4607        let mut scanner = Scanner::new(StrInput::new(" # queued\n# later\n"));
4608
4609        assert!(scanner.skip_yaml_whitespace(true).unwrap());
4610
4611        assert_eq!(scanner.tokens.len(), 1);
4612        assert!(matches!(
4613            scanner.tokens.front().unwrap().1,
4614            QueuedTokenType::Comment(ref comment) if comment.text == " queued"
4615        ));
4616        assert_eq!(scanner.mark.line(), 1);
4617        assert_eq!(scanner.mark.col(), 9);
4618    }
4619
4620    #[test]
4621    fn token_skip_can_stop_after_queued_comment() {
4622        let mut scanner = Scanner::new(StrInput::new("# first\n# second\n"));
4623
4624        assert!(scanner.skip_to_next_token(true).unwrap());
4625
4626        assert_eq!(scanner.tokens.len(), 1);
4627        assert!(matches!(
4628            scanner.tokens.front().unwrap().1,
4629            QueuedTokenType::Comment(ref comment) if comment.text == " first"
4630        ));
4631        assert_eq!(scanner.mark.line(), 2);
4632        assert_eq!(scanner.mark.col(), 0);
4633    }
4634
4635    #[test]
4636    fn scanner_emits_first_leading_comment_before_scanning_next_comment() {
4637        let mut scanner = Scanner::new(StrInput::new("# first\n# second\nkey: value\n"));
4638
4639        assert!(matches!(
4640            scanner.next_token().unwrap().unwrap().1,
4641            TokenType::StreamStart
4642        ));
4643        assert!(matches!(
4644            scanner.next_token().unwrap().unwrap().1,
4645            TokenType::Comment(ref comment) if comment.text == " first"
4646        ));
4647        assert!(scanner.tokens.is_empty());
4648        assert!(matches!(
4649            scanner.next_token().unwrap().unwrap().1,
4650            TokenType::Comment(ref comment) if comment.text == " second"
4651        ));
4652    }
4653
4654    #[test]
4655    fn scanner_emits_quoted_scalar_comment_before_scanning_following_value() {
4656        let mut scanner = Scanner::new(StrInput::new("\"key\" # quoted\n: value\n"));
4657
4658        assert!(matches!(
4659            scanner.next_token().unwrap().unwrap().1,
4660            TokenType::StreamStart
4661        ));
4662        assert!(matches!(
4663            scanner.next_token().unwrap().unwrap().1,
4664            TokenType::Scalar(ScalarStyle::DoubleQuoted, ref value) if value == "key"
4665        ));
4666        assert!(matches!(
4667            scanner.next_token().unwrap().unwrap().1,
4668            TokenType::Comment(ref comment) if comment.text == " quoted"
4669        ));
4670    }
4671
4672    #[test]
4673    fn flow_scalar_comment_disables_adjacent_value_lookahead() {
4674        let mut scanner = Scanner::new(StrInput::new("\"key\"\n# quoted\n: value\n"));
4675
4676        scanner.fetch_flow_scalar(false).unwrap();
4677
4678        assert_eq!(scanner.adjacent_value_allowed_at, usize::MAX);
4679        assert!(matches!(
4680            scanner.tokens.front().unwrap().1,
4681            QueuedTokenType::Scalar(ScalarStyle::DoubleQuoted, ref value) if value == "key"
4682        ));
4683        assert!(scanner.tokens.iter().any(|QueuedToken(_, token)| matches!(
4684            token,
4685            QueuedTokenType::Comment(comment) if comment.text == " quoted"
4686        )));
4687    }
4688
4689    #[test]
4690    fn deferred_error_waits_for_all_comment_tokens() {
4691        let mut scanner = Scanner::new(StrInput::new("# first\n# second\n@\n"));
4692
4693        assert!(matches!(
4694            scanner.next_token().unwrap().unwrap().1,
4695            TokenType::StreamStart
4696        ));
4697        assert!(matches!(
4698            scanner.next_token().unwrap().unwrap().1,
4699            TokenType::Comment(ref comment) if comment.text == " first"
4700        ));
4701        assert!(matches!(
4702            scanner.next_token().unwrap().unwrap().1,
4703            TokenType::Comment(ref comment) if comment.text == " second"
4704        ));
4705
4706        let error = scanner.next_token().unwrap_err();
4707
4708        assert_eq!(
4709            error.kind(),
4710            &ErrorKind::UnexpectedCharacter { character: '@' }
4711        );
4712    }
4713
4714    /// Ensure anchors scanned from `StrInput` are returned as `Cow::Borrowed`.
4715    #[test]
4716    fn anchor_name_is_borrowed_for_str_input() {
4717        let mut scanner = Scanner::new(StrInput::new("&anch\n"));
4718
4719        loop {
4720            let tok = scanner
4721                .next_token()
4722                .expect("valid YAML must scan without errors")
4723                .expect("scanner must eventually produce a token");
4724            if let TokenType::Anchor(name) = tok.1 {
4725                assert!(matches!(name, Cow::Borrowed("anch")));
4726                break;
4727            }
4728        }
4729    }
4730
4731    #[test]
4732    fn anchor_name_rejects_non_printable_control_chars() {
4733        let mut scanner = Scanner::new(StrInput::new("&foo\u{0001}\n"));
4734
4735        scanner.next_token().unwrap();
4736        assert_eq!(
4737            scanner.next_token().unwrap_err().kind(),
4738            &ErrorKind::UnexpectedCharacter {
4739                character: '\u{0001}'
4740            }
4741        );
4742    }
4743
4744    #[test]
4745    fn alias_name_rejects_non_printable_control_chars() {
4746        let mut scanner = Scanner::new(StrInput::new("*foo\u{0001}\n"));
4747
4748        scanner.next_token().unwrap();
4749        assert_eq!(
4750            scanner.next_token().unwrap_err().kind(),
4751            &ErrorKind::UnexpectedCharacter {
4752                character: '\u{0001}'
4753            }
4754        );
4755    }
4756
4757    #[test]
4758    fn alias_name_is_borrowed_for_str_input() {
4759        let mut scanner = Scanner::new(StrInput::new("*anch\n"));
4760
4761        loop {
4762            let tok = scanner
4763                .next_token()
4764                .expect("valid YAML must scan without errors")
4765                .expect("scanner must eventually produce a token");
4766            if let TokenType::Alias(name) = tok.1 {
4767                assert!(matches!(name, Cow::Borrowed("anch")));
4768                break;
4769            }
4770        }
4771    }
4772
4773    #[test]
4774    fn alias_name_scans_colon_as_part_of_name() {
4775        let mut scanner = Scanner::new(StrInput::new("*foo: bar\n"));
4776
4777        loop {
4778            let tok = scanner
4779                .next_token()
4780                .expect("scanner must not fail before alias token")
4781                .expect("scanner must eventually emit an alias token");
4782
4783            if let TokenType::Alias(name) = tok.1 {
4784                assert_eq!(name.as_ref(), "foo:");
4785                break;
4786            }
4787        }
4788    }
4789
4790    #[test]
4791    fn anchor_name_scans_colon_as_part_of_name() {
4792        let mut scanner = Scanner::new(StrInput::new("&foo: bar\n"));
4793
4794        loop {
4795            let tok = scanner
4796                .next_token()
4797                .expect("scanner must not fail before anchor token")
4798                .expect("scanner must eventually emit an anchor token");
4799
4800            if let TokenType::Anchor(name) = tok.1 {
4801                assert_eq!(name.as_ref(), "foo:");
4802                break;
4803            }
4804        }
4805    }
4806
4807    /// Ensure `%TAG` directive handle and prefix are borrowed when they are verbatim (no escapes).
4808    #[test]
4809    fn tag_directive_parts_are_borrowed_for_str_input() {
4810        let mut scanner = Scanner::new(StrInput::new("%TAG !e! tag:example.com,2000:app/\n"));
4811
4812        loop {
4813            let tok = scanner
4814                .next_token()
4815                .expect("valid YAML must scan without errors")
4816                .expect("scanner must eventually produce a token");
4817            if let TokenType::TagDirective(handle, prefix) = tok.1 {
4818                assert!(matches!(handle, Cow::Borrowed("!e!")));
4819                assert!(matches!(prefix, Cow::Borrowed("tag:example.com,2000:app/")));
4820                break;
4821            }
4822        }
4823    }
4824
4825    #[test]
4826    fn tag_directive_parts_are_owned_for_buffered_input() {
4827        let mut scanner = Scanner::new(BufferedInput::new(
4828            "%TAG !e! tag:example.com,2000:app/\n".chars(),
4829        ));
4830
4831        loop {
4832            let tok = scanner
4833                .next_token()
4834                .expect("valid YAML must scan without errors")
4835                .expect("scanner must eventually produce a token");
4836            if let TokenType::TagDirective(handle, prefix) = tok.1 {
4837                assert!(matches!(handle, Cow::Owned(_)));
4838                assert_eq!(&*handle, "!e!");
4839                assert!(matches!(prefix, Cow::Owned(_)));
4840                assert_eq!(&*prefix, "tag:example.com,2000:app/");
4841                break;
4842            }
4843        }
4844    }
4845
4846    #[test]
4847    fn buffered_tag_directive_decodes_prefix_escape() {
4848        let mut scanner = Scanner::new(BufferedInput::new(
4849            "%TAG !e! %74ag:example.com,2000:app/\n".chars(),
4850        ));
4851
4852        loop {
4853            let tok = scanner
4854                .next_token()
4855                .expect("valid YAML must scan without errors")
4856                .expect("scanner must eventually produce a token");
4857            if let TokenType::TagDirective(handle, prefix) = tok.1 {
4858                assert_eq!(&*handle, "!e!");
4859                assert!(matches!(prefix, Cow::Owned(_)));
4860                assert_eq!(&*prefix, "tag:example.com,2000:app/");
4861                break;
4862            }
4863        }
4864    }
4865
4866    #[test]
4867    fn local_tag_combines_handle_text_with_escaped_suffix() {
4868        let mut scanner = Scanner::new(StrInput::new("!foo%20bar value\n"));
4869
4870        loop {
4871            let tok = scanner
4872                .next_token()
4873                .expect("valid YAML must scan without errors")
4874                .expect("scanner must eventually produce a token");
4875            if let TokenType::Tag(handle, suffix) = tok.1 {
4876                assert!(matches!(handle, Cow::Borrowed("!")));
4877                assert!(matches!(suffix, Cow::Owned(_)));
4878                assert_eq!(&*suffix, "foo bar");
4879                break;
4880            }
4881        }
4882    }
4883
4884    #[test]
4885    fn secondary_tag_requires_suffix_in_borrowed_and_buffered_paths() {
4886        assert_eq!(
4887            first_scanner_error_kind("!! value\n"),
4888            ErrorKind::MissingTagUri
4889        );
4890        assert_eq!(
4891            first_buffered_scanner_error_kind("!! value\n"),
4892            ErrorKind::MissingTagUri
4893        );
4894    }
4895
4896    #[test]
4897    fn plain_scalar_is_borrowed_when_whitespace_free_for_str_input() {
4898        let mut scanner = Scanner::new(StrInput::new("foo\n"));
4899
4900        loop {
4901            let tok = scanner
4902                .next_token()
4903                .expect("valid YAML must scan without errors")
4904                .expect("scanner must eventually produce a token");
4905            if let TokenType::Scalar(_, value) = tok.1 {
4906                assert!(matches!(value, Cow::Borrowed("foo")));
4907                break;
4908            }
4909        }
4910    }
4911
4912    #[test]
4913    fn plain_scalar_is_borrowed_when_whitespace_present_for_str_input() {
4914        let mut scanner = Scanner::new(StrInput::new("foo bar\n"));
4915
4916        loop {
4917            let tok = scanner
4918                .next_token()
4919                .expect("valid YAML must scan without errors")
4920                .expect("scanner must eventually produce a token");
4921            if let TokenType::Scalar(_, value) = tok.1 {
4922                assert!(matches!(value, Cow::Borrowed("foo bar")));
4923                break;
4924            }
4925        }
4926    }
4927
4928    #[test]
4929    fn single_quoted_scalar_is_borrowed_when_verbatim_for_str_input() {
4930        let mut scanner = Scanner::new(StrInput::new("'foo bar'\n"));
4931
4932        loop {
4933            let tok = scanner
4934                .next_token()
4935                .expect("valid YAML must scan without errors")
4936                .expect("scanner must eventually produce a token");
4937            if let TokenType::Scalar(_, value) = tok.1 {
4938                assert!(matches!(value, Cow::Borrowed("foo bar")));
4939                break;
4940            }
4941        }
4942    }
4943
4944    #[test]
4945    fn single_quoted_scalar_is_owned_when_quote_is_escaped_for_str_input() {
4946        let mut scanner = Scanner::new(StrInput::new("'foo''bar'\n"));
4947
4948        loop {
4949            let tok = scanner
4950                .next_token()
4951                .expect("valid YAML must scan without errors")
4952                .expect("scanner must eventually produce a token");
4953            if let TokenType::Scalar(_, value) = tok.1 {
4954                assert!(matches!(value, Cow::Owned(_)));
4955                assert_eq!(&*value, "foo'bar");
4956                break;
4957            }
4958        }
4959    }
4960
4961    #[test]
4962    fn double_quoted_scalar_is_borrowed_when_verbatim_for_str_input() {
4963        let mut scanner = Scanner::new(StrInput::new("\"foo bar\"\n"));
4964
4965        loop {
4966            let tok = scanner
4967                .next_token()
4968                .expect("valid YAML must scan without errors")
4969                .expect("scanner must eventually produce a token");
4970            if let TokenType::Scalar(_, value) = tok.1 {
4971                assert!(matches!(value, Cow::Borrowed("foo bar")));
4972                break;
4973            }
4974        }
4975    }
4976
4977    #[test]
4978    fn double_quoted_scalar_is_owned_when_escape_sequence_present_for_str_input() {
4979        let mut scanner = Scanner::new(StrInput::new("\"foo\\nbar\"\n"));
4980
4981        loop {
4982            let tok = scanner
4983                .next_token()
4984                .expect("valid YAML must scan without errors")
4985                .expect("scanner must eventually produce a token");
4986            if let TokenType::Scalar(_, value) = tok.1 {
4987                assert!(matches!(value, Cow::Owned(_)));
4988                assert_eq!(&*value, "foo\nbar");
4989                break;
4990            }
4991        }
4992    }
4993
4994    #[test]
4995    fn plain_key_is_borrowed_for_str_input() {
4996        // Keys are just scalars in a key position; they should also be borrowed.
4997        let mut scanner = Scanner::new(StrInput::new("mykey: value\n"));
4998
4999        let mut found_key = false;
5000        let mut key_value: Option<Cow<'_, str>> = None;
5001
5002        loop {
5003            let tok = scanner
5004                .next_token()
5005                .expect("valid YAML must scan without errors");
5006            let Some(tok) = tok else { break };
5007
5008            if matches!(tok.1, TokenType::Key) {
5009                found_key = true;
5010            } else if found_key {
5011                if let TokenType::Scalar(_, value) = tok.1 {
5012                    key_value = Some(value);
5013                    break;
5014                }
5015            }
5016        }
5017
5018        assert!(found_key, "expected to find a Key token");
5019        let key_value = key_value.expect("expected to find a scalar after Key token");
5020        assert!(
5021            matches!(key_value, Cow::Borrowed("mykey")),
5022            "key should be borrowed, got: {key_value:?}"
5023        );
5024    }
5025
5026    #[test]
5027    fn quoted_key_is_borrowed_when_verbatim_for_str_input() {
5028        let mut scanner = Scanner::new(StrInput::new("\"mykey\": value\n"));
5029
5030        let mut found_key = false;
5031        let mut key_value: Option<Cow<'_, str>> = None;
5032
5033        loop {
5034            let tok = scanner
5035                .next_token()
5036                .expect("valid YAML must scan without errors");
5037            let Some(tok) = tok else { break };
5038
5039            if matches!(tok.1, TokenType::Key) {
5040                found_key = true;
5041            } else if found_key {
5042                if let TokenType::Scalar(_, value) = tok.1 {
5043                    key_value = Some(value);
5044                    break;
5045                }
5046            }
5047        }
5048
5049        assert!(found_key, "expected to find a Key token");
5050        let key_value = key_value.expect("expected to find a scalar after Key token");
5051        assert!(
5052            matches!(key_value, Cow::Borrowed("mykey")),
5053            "quoted key should be borrowed when verbatim, got: {key_value:?}"
5054        );
5055    }
5056
5057    #[test]
5058    fn tag_handle_and_suffix_are_borrowed_for_str_input() {
5059        // Test a tag like !!str which should have handle="!!" and suffix="str"
5060        let mut scanner = Scanner::new(StrInput::new("!!str foo\n"));
5061
5062        loop {
5063            let tok = scanner
5064                .next_token()
5065                .expect("valid YAML must scan without errors")
5066                .expect("scanner must eventually produce a token");
5067            if let TokenType::Tag(handle, suffix) = tok.1 {
5068                assert!(
5069                    matches!(handle, Cow::Borrowed("!!")),
5070                    "tag handle should be borrowed, got: {handle:?}"
5071                );
5072                assert!(
5073                    matches!(suffix, Cow::Borrowed("str")),
5074                    "tag suffix should be borrowed, got: {suffix:?}"
5075                );
5076                break;
5077            }
5078        }
5079    }
5080
5081    #[test]
5082    fn local_tag_suffix_is_borrowed_for_str_input() {
5083        // Test a local tag like !mytag which should have handle="!" and suffix="mytag"
5084        let mut scanner = Scanner::new(StrInput::new("!mytag foo\n"));
5085
5086        loop {
5087            let tok = scanner
5088                .next_token()
5089                .expect("valid YAML must scan without errors")
5090                .expect("scanner must eventually produce a token");
5091            if let TokenType::Tag(handle, suffix) = tok.1 {
5092                assert!(
5093                    matches!(handle, Cow::Borrowed("!")),
5094                    "local tag handle should be '!', got: {handle:?}"
5095                );
5096                assert!(
5097                    matches!(suffix, Cow::Borrowed("mytag")),
5098                    "local tag suffix should be borrowed, got: {suffix:?}"
5099                );
5100                break;
5101            }
5102        }
5103    }
5104
5105    #[test]
5106    fn local_tag_suffix_with_punctuation_is_borrowed_for_str_input() {
5107        let mut scanner = Scanner::new(StrInput::new("!mytag/part foo\n"));
5108
5109        loop {
5110            let tok = scanner
5111                .next_token()
5112                .expect("valid YAML must scan without errors")
5113                .expect("scanner must eventually produce a token");
5114            if let TokenType::Tag(handle, suffix) = tok.1 {
5115                assert!(matches!(handle, Cow::Borrowed("!")));
5116                assert!(matches!(suffix, Cow::Borrowed("mytag/part")));
5117                break;
5118            }
5119        }
5120    }
5121
5122    #[test]
5123    fn tag_with_uri_escape_is_owned_for_str_input() {
5124        // Test a tag with URI escape like !my%20tag - suffix must be owned due to decoding
5125        let mut scanner = Scanner::new(StrInput::new("!!my%20tag foo\n"));
5126
5127        loop {
5128            let tok = scanner
5129                .next_token()
5130                .expect("valid YAML must scan without errors")
5131                .expect("scanner must eventually produce a token");
5132            if let TokenType::Tag(handle, suffix) = tok.1 {
5133                assert!(
5134                    matches!(handle, Cow::Borrowed("!!")),
5135                    "tag handle should still be borrowed, got: {handle:?}"
5136                );
5137                assert!(
5138                    matches!(suffix, Cow::Owned(_)),
5139                    "tag suffix with URI escape should be owned, got: {suffix:?}"
5140                );
5141                assert_eq!(&*suffix, "my tag");
5142                break;
5143            }
5144        }
5145    }
5146
5147    #[test]
5148    fn flow_scalar_buffer_tracks_pending_whitespace() {
5149        let mut borrowed = super::FlowScalarBuf::new_borrowed(2);
5150
5151        borrowed.note_pending_ws(5, 8);
5152        borrowed.commit_pending_ws();
5153        assert!(matches!(
5154            borrowed,
5155            super::FlowScalarBuf::Borrowed {
5156                end: 8,
5157                pending_ws_start: None,
5158                pending_ws_end: 8,
5159                ..
5160            }
5161        ));
5162
5163        borrowed.note_pending_ws(9, 11);
5164        borrowed.discard_pending_ws();
5165        assert!(matches!(
5166            borrowed,
5167            super::FlowScalarBuf::Borrowed {
5168                end: 8,
5169                pending_ws_start: None,
5170                pending_ws_end: 8,
5171                ..
5172            }
5173        ));
5174        assert!(borrowed.as_owned_mut().is_none());
5175
5176        let mut owned = super::FlowScalarBuf::new_owned();
5177        owned.as_owned_mut().unwrap().push_str("owned");
5178        assert!(matches!(owned, super::FlowScalarBuf::Owned(ref s) if s == "owned"));
5179    }
5180
5181    fn first_scanner_error_kind(input: &str) -> ErrorKind {
5182        first_scanner_error(input).kind().clone()
5183    }
5184
5185    fn first_buffered_scanner_error_kind(input: &str) -> ErrorKind {
5186        let mut scanner = Scanner::new(BufferedInput::new(input.chars()));
5187        loop {
5188            match scanner.next_token() {
5189                Ok(Some(_)) => {}
5190                Ok(None) => panic!("expected scanner error"),
5191                Err(error) => return error.kind().clone(),
5192            }
5193        }
5194    }
5195
5196    fn first_scanner_error(input: &str) -> ScanError {
5197        let mut scanner = Scanner::new(StrInput::new(input));
5198        loop {
5199            match scanner.next_token() {
5200                Ok(Some(_)) => {}
5201                Ok(None) => panic!("expected scanner error"),
5202                Err(error) => return error,
5203            }
5204        }
5205    }
5206
5207    fn first_scalar_value(input: &str) -> String {
5208        let mut scanner = Scanner::new(StrInput::new(input));
5209        loop {
5210            match scanner.next_token().expect("scanner should not error") {
5211                Some(Token(_, TokenType::Scalar(_, value))) => return value.into_owned(),
5212                Some(_) => {}
5213                None => panic!("expected scalar token"),
5214            }
5215        }
5216    }
5217
5218    fn first_buffered_scalar_value(input: &str) -> String {
5219        let mut scanner = Scanner::new(BufferedInput::new(input.chars()));
5220        loop {
5221            match scanner.next_token().expect("scanner should not error") {
5222                Some(Token(_, TokenType::Scalar(_, value))) => return value.into_owned(),
5223                Some(_) => {}
5224                None => panic!("expected scalar token"),
5225            }
5226        }
5227    }
5228
5229    #[test]
5230    fn iterator_next_emits_error_and_then_stays_empty() {
5231        let mut scanner = Scanner::new(StrInput::new("\"unterminated"));
5232
5233        let error = scanner
5234            .by_ref()
5235            .find_map(Result::err)
5236            .expect("scanner should emit the error");
5237        assert_eq!(error.kind(), &ErrorKind::UnclosedQuotedScalar);
5238        assert!(scanner.next().is_none());
5239    }
5240
5241    #[test]
5242    fn next_token_returns_none_after_stream_end() {
5243        let mut scanner = Scanner::new(StrInput::new(""));
5244
5245        while let Some(token) = scanner.next_token().unwrap() {
5246            if matches!(token.1, TokenType::StreamEnd) {
5247                break;
5248            }
5249        }
5250
5251        assert!(scanner.stream_started());
5252        assert!(scanner.stream_ended());
5253        assert!(scanner.next_token().unwrap().is_none());
5254    }
5255
5256    #[test]
5257    fn directive_name_must_be_present() {
5258        assert_eq!(
5259            first_scanner_error_kind("%\n"),
5260            ErrorKind::MissingDirectiveName
5261        );
5262    }
5263
5264    #[test]
5265    fn yaml_directive_requires_dot_between_version_numbers() {
5266        assert_eq!(
5267            first_scanner_error_kind("%YAML 1\n"),
5268            ErrorKind::MissingYamlVersionSeparator
5269        );
5270    }
5271
5272    #[test]
5273    fn yaml_directive_requires_major_version_number() {
5274        assert_eq!(
5275            first_scanner_error_kind("%YAML .2\n"),
5276            ErrorKind::MissingYamlVersion
5277        );
5278    }
5279
5280    #[test]
5281    fn yaml_directive_rejects_extremely_long_version_number() {
5282        assert_eq!(
5283            first_scanner_error_kind("%YAML 1234567890.2\n"),
5284            ErrorKind::YamlVersionTooLong
5285        );
5286    }
5287
5288    #[test]
5289    fn tag_directive_handle_must_end_with_bang() {
5290        assert_eq!(
5291            first_scanner_error_kind("%TAG !bad tag:example.com,2024:\n"),
5292            ErrorKind::ExpectedTagDirectiveBang
5293        );
5294    }
5295
5296    #[test]
5297    fn tag_directive_handle_must_start_with_bang() {
5298        assert_eq!(
5299            first_scanner_error_kind("%TAG bad! tag:example.com,2024:\n"),
5300            ErrorKind::ExpectedTagBang
5301        );
5302        assert_eq!(
5303            first_buffered_scanner_error_kind("%TAG bad! tag:example.com,2024:\n"),
5304            ErrorKind::ExpectedTagBang
5305        );
5306    }
5307
5308    #[test]
5309    fn tag_directive_prefix_must_start_with_tag_character() {
5310        assert_eq!(
5311            first_scanner_error_kind("%TAG !e! `bad\n"),
5312            ErrorKind::InvalidGlobalTagCharacter
5313        );
5314    }
5315
5316    #[test]
5317    fn tag_directive_prefix_must_end_before_invalid_content() {
5318        assert_eq!(
5319            first_scanner_error_kind("%TAG !e! tag:example.com^suffix\n"),
5320            ErrorKind::InvalidTagDirectiveTerminator
5321        );
5322    }
5323
5324    #[test]
5325    fn tag_directive_prefix_with_uri_escape_is_owned_and_decoded() {
5326        let mut scanner =
5327            Scanner::new(StrInput::new("%TAG !e! tag:example.com,2024:some%20app/\n"));
5328
5329        loop {
5330            let token = scanner
5331                .next_token()
5332                .expect("valid directive should scan")
5333                .expect("scanner must produce a directive token");
5334            if let TokenType::TagDirective(handle, prefix) = token.1 {
5335                assert!(matches!(handle, Cow::Borrowed("!e!")));
5336                assert!(matches!(prefix, Cow::Owned(_)));
5337                assert_eq!(&*prefix, "tag:example.com,2024:some app/");
5338                break;
5339            }
5340        }
5341    }
5342
5343    #[test]
5344    fn bare_bang_tag_scans_as_non_specific_tag() {
5345        let mut scanner = Scanner::new(StrInput::new("! foo\n"));
5346
5347        loop {
5348            let token = scanner
5349                .next_token()
5350                .expect("valid tag should scan")
5351                .expect("scanner must produce a tag token");
5352            if let TokenType::Tag(handle, suffix) = token.1 {
5353                assert_eq!(&*handle, "");
5354                assert_eq!(&*suffix, "!");
5355                break;
5356            }
5357        }
5358    }
5359
5360    #[test]
5361    fn tag_requires_separation_after_suffix() {
5362        assert_eq!(
5363            first_scanner_error_kind("!foo,bar\n"),
5364            ErrorKind::InvalidTagTerminator
5365        );
5366    }
5367
5368    #[test]
5369    fn verbatim_tag_requires_uri() {
5370        assert_eq!(
5371            first_scanner_error_kind("!<> foo\n"),
5372            ErrorKind::MissingTagUri
5373        );
5374    }
5375
5376    #[test]
5377    fn verbatim_tag_requires_closing_angle_bracket() {
5378        assert_eq!(
5379            first_scanner_error_kind("!<tag:yaml.org,2002:str foo\n"),
5380            ErrorKind::UnclosedVerbatimTag
5381        );
5382    }
5383
5384    #[test]
5385    fn tag_uri_escape_requires_hex_digits() {
5386        assert_eq!(
5387            first_scanner_error_kind("!!bad%zz foo\n"),
5388            ErrorKind::InvalidTagEscape
5389        );
5390    }
5391
5392    #[test]
5393    fn tag_uri_escape_rejects_bad_leading_utf8_byte() {
5394        assert_eq!(
5395            first_scanner_error_kind("!!bad%80 foo\n"),
5396            ErrorKind::InvalidTagUtf8LeadingByte
5397        );
5398    }
5399
5400    #[test]
5401    fn tag_uri_escape_rejects_bad_trailing_utf8_byte() {
5402        assert_eq!(
5403            first_scanner_error_kind("!!bad%C2%41 foo\n"),
5404            ErrorKind::InvalidTagUtf8TrailingByte
5405        );
5406    }
5407
5408    #[test]
5409    fn tag_uri_escape_rejects_invalid_utf8_codepoint() {
5410        assert_eq!(
5411            first_scanner_error_kind("!!bad%F4%90%80%80 foo\n"),
5412            ErrorKind::InvalidTagUtf8
5413        );
5414    }
5415
5416    #[test]
5417    fn anchors_and_aliases_require_names() {
5418        assert_eq!(
5419            first_scanner_error_kind("& \n"),
5420            ErrorKind::MissingAnchorOrAliasName
5421        );
5422        assert_eq!(
5423            first_scanner_error_kind("* \n"),
5424            ErrorKind::MissingAnchorOrAliasName
5425        );
5426    }
5427
5428    #[test]
5429    fn document_end_marker_rejects_trailing_content() {
5430        assert_eq!(
5431            first_scanner_error_kind("... trailing\n"),
5432            ErrorKind::InvalidDocumentEnd
5433        );
5434    }
5435
5436    #[test]
5437    fn reserved_indicators_are_rejected_outside_directives() {
5438        let error = first_scanner_error(" @\n");
5439
5440        assert_eq!(
5441            error.kind(),
5442            &ErrorKind::UnexpectedCharacter { character: '@' }
5443        );
5444    }
5445
5446    #[test]
5447    fn flow_block_entry_indicator_is_rejected() {
5448        assert_eq!(
5449            first_scanner_error_kind("[- ]\n"),
5450            ErrorKind::BlockEntryInFlowCollection
5451        );
5452    }
5453
5454    #[test]
5455    fn block_entry_after_tabbed_separator_reports_specific_error() {
5456        assert_eq!(
5457            first_scanner_error_kind("-\t- value\n"),
5458            ErrorKind::InvalidBlockEntryWhitespace
5459        );
5460    }
5461
5462    #[test]
5463    fn document_indicator_reports_unclosed_flow_collection() {
5464        let error = first_scanner_error("[\n---\n");
5465
5466        assert_eq!(
5467            error.kind(),
5468            &ErrorKind::UnclosedFlowCollection { open: '[' }
5469        );
5470    }
5471
5472    #[test]
5473    fn block_scalar_header_rejects_trailing_content() {
5474        assert_eq!(
5475            first_scanner_error_kind("|+ trailing\n"),
5476            ErrorKind::InvalidBlockScalarHeader
5477        );
5478    }
5479
5480    #[test]
5481    fn block_scalar_rejects_zero_indent_indicator() {
5482        assert_eq!(
5483            first_scanner_error_kind("|0\n"),
5484            ErrorKind::ZeroBlockScalarIndent
5485        );
5486        assert_eq!(
5487            first_scanner_error_kind("|+0\n"),
5488            ErrorKind::ZeroBlockScalarIndent
5489        );
5490    }
5491
5492    #[test]
5493    fn empty_block_scalar_at_eof_honors_chomping() {
5494        assert_eq!(first_scalar_value("|\n"), "");
5495        assert_eq!(first_scalar_value("|-\n"), "");
5496        assert_eq!(first_scalar_value("|+\n"), "");
5497        assert_eq!(first_scalar_value("|+\n\n"), "\n");
5498        assert_eq!(first_scalar_value("|+\n   "), "\n");
5499    }
5500
5501    #[test]
5502    fn buffered_block_scalar_reads_content_past_lookahead_window() {
5503        assert_eq!(
5504            first_buffered_scalar_value("|\n  abcdefghijklmnopqrstuvwxyz\n"),
5505            "abcdefghijklmnopqrstuvwxyz\n"
5506        );
5507    }
5508
5509    #[test]
5510    fn explicit_indent_block_scalar_can_end_at_document_marker() {
5511        assert_eq!(first_scalar_value("|1\n...\n"), "");
5512    }
5513
5514    #[test]
5515    fn root_explicit_indent_block_scalar_rejects_underindented_content() {
5516        assert_eq!(
5517            first_scanner_error_kind("|2\nx\n"),
5518            ErrorKind::InvalidBlockScalarIndent
5519        );
5520    }
5521
5522    #[test]
5523    fn quoted_scalar_rejects_document_indicator_at_line_start() {
5524        assert_eq!(
5525            first_scanner_error_kind("\"one\n---\ntwo\"\n"),
5526            ErrorKind::DocumentIndicatorInQuotedScalar
5527        );
5528    }
5529
5530    #[test]
5531    fn quoted_scalar_rejects_tab_indentation_after_line_break() {
5532        assert_eq!(
5533            first_scanner_error_kind("a: \"one\n\tbad\"\n"),
5534            ErrorKind::TabInIndentation
5535        );
5536    }
5537
5538    #[test]
5539    fn quoted_scalar_rejects_underindented_continuation() {
5540        assert_eq!(
5541            first_scanner_error_kind("a: \"one\nbad\"\n"),
5542            ErrorKind::InvalidQuotedScalarIndent
5543        );
5544    }
5545
5546    #[test]
5547    fn quoted_scalar_trailing_content_error_names_quote_style() {
5548        assert_eq!(
5549            first_scanner_error_kind("'foo' trailing\n"),
5550            ErrorKind::InvalidTrailingSingleQuotedScalar
5551        );
5552        assert_eq!(
5553            first_scanner_error_kind("\"foo\" trailing\n"),
5554            ErrorKind::InvalidTrailingDoubleQuotedScalar
5555        );
5556    }
5557
5558    #[test]
5559    fn quoted_scalar_escape_errors_cover_hex_and_surrogate_edges() {
5560        assert_eq!(
5561            first_scanner_error_kind("\"\\xG0\"\n"),
5562            ErrorKind::InvalidQuotedScalarHexEscape
5563        );
5564        assert_eq!(
5565            first_scanner_error_kind("\"\\uD800\\uGGGG\"\n"),
5566            ErrorKind::InvalidLowSurrogateHexEscape
5567        );
5568        assert_eq!(
5569            first_scanner_error_kind("\"\\uD800\\u0041\"\n"),
5570            ErrorKind::InvalidLowSurrogate
5571        );
5572        assert_eq!(
5573            first_scanner_error_kind("\"\\U00110000\"\n"),
5574            ErrorKind::InvalidUnicodeEscape
5575        );
5576    }
5577
5578    #[test]
5579    fn indented_flow_scalar_reports_invalid_indentation() {
5580        assert_eq!(
5581            first_scanner_error_kind("a:\n  [\nfoo]\n"),
5582            ErrorKind::InvalidIndentation
5583        );
5584    }
5585
5586    #[test]
5587    fn required_simple_key_requires_value_at_stream_end() {
5588        let error = first_scanner_error("a:\n&b\n- c\n");
5589
5590        assert_eq!(error.kind(), &ErrorKind::SimpleKeyExpected);
5591        assert_eq!(error.marker().index(), 3);
5592        assert_eq!(error.marker().line(), 2);
5593        assert_eq!(error.marker().col(), 0);
5594    }
5595
5596    #[test]
5597    fn plain_scalar_rejects_dash_before_flow_indicator() {
5598        assert_eq!(
5599            first_scanner_error_kind("[-]\n"),
5600            ErrorKind::PlainScalarStartsWithDashFlowIndicator
5601        );
5602    }
5603
5604    #[test]
5605    fn explicit_key_rejects_tab_after_indicator() {
5606        assert_eq!(
5607            first_scanner_error_kind("? \tfoo\n"),
5608            ErrorKind::TabNotAllowed
5609        );
5610    }
5611
5612    #[test]
5613    fn flow_mapping_rejects_adjacent_collection_value_after_plain_key() {
5614        assert_eq!(
5615            first_scanner_error_kind("[a:[]]\n"),
5616            ErrorKind::FlowMappingValueAdjacentCollection
5617        );
5618    }
5619
5620    #[test]
5621    fn implicit_flow_mapping_colon_cannot_move_to_next_line() {
5622        assert_eq!(
5623            first_scanner_error_kind("[foo\n: bar]\n"),
5624            ErrorKind::InvalidColonPlacement
5625        );
5626    }
5627
5628    #[test]
5629    fn invalid_simple_key_token_positions_are_scan_errors() {
5630        for (tokens_parsed, token_number) in [(1, 0), (0, 1)] {
5631            let mut scanner = Scanner::new(StrInput::new(": value\n"));
5632            scanner.fetch_stream_start();
5633            scanner.tokens.clear();
5634            scanner.tokens_parsed = tokens_parsed;
5635
5636            let simple_key = scanner
5637                .simple_keys
5638                .last_mut()
5639                .expect("stream start should create a simple key slot");
5640            simple_key.possible = true;
5641            simple_key.token_number = token_number;
5642
5643            let error = scanner
5644                .fetch_value()
5645                .expect_err("invalid simple key position should be reported as a scan error");
5646            assert_eq!(error.kind(), &ErrorKind::InvalidSimpleKey);
5647            assert_eq!(error.marker(), &Marker::new(0, 1, 0));
5648        }
5649    }
5650
5651    #[test]
5652    fn issue14_alias_scanner_consumes_colon_as_name_character() {
5653        let mut scanner = Scanner::new(StrInput::new("*foo: bar\n"));
5654
5655        assert!(matches!(
5656            scanner.next_token().unwrap().unwrap().1,
5657            TokenType::StreamStart
5658        ));
5659
5660        let token = scanner.next_token().unwrap().unwrap();
5661
5662        assert!(
5663            matches!(token.1, TokenType::Alias(ref name) if name.as_ref() == "foo:"),
5664            "expected `*foo: bar` to start with Alias(\"foo:\"), got {token:?}"
5665        );
5666    }
5667
5668    #[test]
5669    fn issue14_anchor_scanner_consumes_colon_as_name_character() {
5670        let mut scanner = Scanner::new(StrInput::new("&foo: bar\n"));
5671
5672        assert!(matches!(
5673            scanner.next_token().unwrap().unwrap().1,
5674            TokenType::StreamStart
5675        ));
5676
5677        let token = scanner.next_token().unwrap().unwrap();
5678
5679        assert!(
5680            matches!(token.1, TokenType::Anchor(ref name) if name.as_ref() == "foo:"),
5681            "expected `&foo: bar` to start with Anchor(\"foo:\"), got {token:?}"
5682        );
5683    }
5684}