Skip to main content

granit_parser/
scanner.rs

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