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