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