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