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        // Flow entries and delimiters may be under-indented for compatibility with
1620        // PyYAML and ruamel.yaml. Block indentation was handled by `unroll_indent`.
1621        let c = self.input.peek();
1622        let nc = self.input.peek_nth(1);
1623        match c {
1624            '[' => self.fetch_flow_collection_start(TokenType::FlowSequenceStart),
1625            '{' => self.fetch_flow_collection_start(TokenType::FlowMappingStart),
1626            ']' => self.fetch_flow_collection_end(TokenType::FlowSequenceEnd, '[', ']'),
1627            '}' => self.fetch_flow_collection_end(TokenType::FlowMappingEnd, '{', '}'),
1628            ',' => self.fetch_flow_entry(),
1629            '-' if is_blank_or_breakz(nc) => self.fetch_block_entry(),
1630            '?' if is_blank_or_breakz(nc) => self.fetch_key(),
1631            ':' if is_blank_or_breakz(nc) => self.fetch_value(),
1632            ':' if self.flow_level > 0
1633                && (is_flow(nc) || self.mark.index() == self.adjacent_value_allowed_at) =>
1634            {
1635                self.fetch_flow_value()
1636            }
1637            // Is it an alias?
1638            '*' => self.fetch_anchor(true),
1639            // Is it an anchor?
1640            '&' => self.fetch_anchor(false),
1641            '!' => self.fetch_tag(),
1642            // Is it a literal scalar?
1643            '|' if self.flow_level == 0 => self.fetch_block_scalar(true),
1644            // Is it a folded scalar?
1645            '>' if self.flow_level == 0 => self.fetch_block_scalar(false),
1646            '\'' => self.fetch_flow_scalar(true),
1647            '"' => self.fetch_flow_scalar(false),
1648            // plain scalar
1649            '-' if !is_blank_or_breakz(nc) => self.fetch_plain_scalar(),
1650            ':' | '?' if !is_blank_or_breakz(nc) && self.flow_level == 0 => {
1651                self.fetch_plain_scalar()
1652            }
1653            c if is_bom(c) => Err(self.scan_error(ErrorKind::BomInsideDocument)),
1654            '|' | '>' | '%' | '@' | '`' => {
1655                Err(self.scan_error(ErrorKind::UnexpectedCharacter { character: c }))
1656            }
1657            _ => self.fetch_plain_scalar(),
1658        }
1659    }
1660
1661    /// Return the next compact queued token, scanning more input when needed.
1662    ///
1663    /// # Errors
1664    /// Returns `ScanError` when scanning fails to find an expected next token.
1665    pub(crate) fn next_queued_token(&mut self) -> Result<Option<QueuedToken<'input>>, ScanError> {
1666        if self.deferred_error.is_some() {
1667            if !matches!(
1668                self.tokens.front().map(|token| &token.1),
1669                Some(QueuedTokenType::Comment(_))
1670            ) {
1671                if let Some(error) = self.deferred_error.take() {
1672                    return error.into_result();
1673                }
1674            }
1675            self.token_available = true;
1676        }
1677
1678        if self.stream_end_produced {
1679            return Ok(None);
1680        }
1681
1682        if !self.token_available {
1683            if let Err(error) = self.fetch_more_tokens() {
1684                if matches!(
1685                    self.tokens.front().map(|token| &token.1),
1686                    Some(QueuedTokenType::Comment(_))
1687                ) {
1688                    self.deferred_error = Some(error);
1689                } else {
1690                    return Err(error);
1691                }
1692            }
1693        }
1694        let Some(t) = self.tokens.pop_front() else {
1695            unreachable!("fetch_more_tokens succeeded without producing a token")
1696        };
1697        self.token_available = false;
1698        self.tokens_parsed += 1;
1699
1700        let is_stream_end = matches!(t.1, QueuedTokenType::StreamEnd);
1701        if is_stream_end {
1702            self.stream_end_produced = true;
1703        }
1704        Ok(Some(t))
1705    }
1706
1707    /// Return the next queued token, scanning more input when needed.
1708    ///
1709    /// # Errors
1710    /// Returns `ScanError` when scanning fails to find an expected next token.
1711    fn next_token(&mut self) -> Result<Option<Token<'input>>, ScanError> {
1712        Ok(self.next_queued_token()?.map(QueuedToken::into_public))
1713    }
1714
1715    /// Scan more input until a token is ready to be returned.
1716    ///
1717    /// # Errors
1718    /// Returns `ScanError` when scanning fails.
1719    fn fetch_more_tokens(&mut self) -> ScanResult {
1720        let mut need_more;
1721        loop {
1722            if self.tokens.is_empty() {
1723                need_more = true;
1724            } else {
1725                need_more = false;
1726                // Stale potential keys that we know won't be keys.
1727                self.stale_simple_keys()?;
1728                if !matches!(
1729                    self.tokens.front().map(|token| &token.1),
1730                    Some(QueuedTokenType::Comment(_))
1731                ) {
1732                    // If our next token to be emitted may be a key, fetch more context.
1733                    for sk in &self.simple_keys {
1734                        if sk.possible && sk.token_number == self.tokens_parsed {
1735                            need_more = true;
1736                            break;
1737                        }
1738                    }
1739                }
1740            }
1741
1742            // Stop fetching immediately after document end/start markers
1743            // to allow the parser to emit the event before reading more content.
1744            if let Some(token) = self.tokens.back() {
1745                if matches!(
1746                    token.1,
1747                    QueuedTokenType::DocumentEnd | QueuedTokenType::DocumentStart
1748                ) {
1749                    break;
1750                }
1751            }
1752
1753            if !need_more {
1754                break;
1755            }
1756            self.fetch_next_token()?;
1757        }
1758        self.token_available = true;
1759
1760        Ok(())
1761    }
1762
1763    /// Mark simple keys that can no longer be keys as such.
1764    ///
1765    /// This function sets `possible` to `false` to each key that, now we have more context, we
1766    /// know will not be keys.
1767    ///
1768    /// # Errors
1769    /// This function returns an error if one of the keys becoming impossible was required to be a
1770    /// key.
1771    fn stale_simple_keys(&mut self) -> ScanResult {
1772        for sk in &mut self.simple_keys {
1773            let is_line_stale = self.flow_level == 0 && sk.mark.line < self.mark.line;
1774            // The length cap applies in flow contexts too; otherwise token buffering can grow
1775            // without bound while the scanner waits to see whether a later ':' resolves the key.
1776            let is_length_stale = self.mark.index().saturating_sub(sk.mark.index())
1777                > self.options.simple_key_max_lookahead;
1778
1779            if sk.possible && (is_line_stale || is_length_stale) {
1780                if sk.required {
1781                    return Err(Self::simple_key_expected(sk.mark));
1782                }
1783                sk.possible = false;
1784            }
1785        }
1786        Ok(())
1787    }
1788
1789    /// Skip over whitespace (`\t`, ` `, `\n`, `\r`) until the next non-comment token.
1790    ///
1791    /// When comment emission is enabled, encountered comments are queued as
1792    /// [`TokenType::Comment`] tokens so the parser can emit them as presentation events. The
1793    /// function returns after one comment, whether it was queued or ignored, so callers can
1794    /// publish completed syntax before scanning later input that may fail.
1795    ///
1796    /// # Errors
1797    /// This function returns an error if a tab is encountered where there should not be
1798    /// one.
1799    fn skip_to_next_token(&mut self) -> Result<SkipToNextTokenOutcome, ScanError> {
1800        // Hot-path helper: consume a single logical line break and apply simple-key rules.
1801        // (Kept local to ensure the compiler can inline it easily.)
1802        let consume_linebreak = |this: &mut Self| {
1803            this.input.lookahead(2);
1804            this.skip_linebreak();
1805            if this.flow_level == 0 {
1806                this.allow_simple_key();
1807            }
1808        };
1809
1810        let mut outcome = SkipToNextTokenOutcome::default();
1811        loop {
1812            let ch = self.input.look_ch();
1813            if self.explicit_key_tab_check_pending {
1814                match ch {
1815                    '\t' => {
1816                        return Err(self.scan_error(ErrorKind::TabNotAllowed));
1817                    }
1818                    ' ' | '\n' | '\r' | '#' => {}
1819                    _ => self.explicit_key_tab_check_pending = false,
1820                }
1821            }
1822
1823            match ch {
1824                // Tabs may not be used as indentation (block context only).
1825                '\t' => {
1826                    if self.is_within_block()
1827                        && self.leading_whitespace
1828                        && (self.mark.col as isize) < self.indent
1829                    {
1830                        self.skip_ws_to_eol(SkipTabs::Yes)?;
1831
1832                        // If we have content on that line with a tab, return an error.
1833                        if !self.input.next_is_breakz() {
1834                            return Err(self.scan_error(ErrorKind::TabInBlockIndentation));
1835                        }
1836
1837                        // Micro-opt: if we stopped on a line break, consume it now (avoids another loop trip).
1838                        if matches!(self.input.look_ch(), '\n' | '\r') {
1839                            consume_linebreak(self);
1840                        }
1841                    } else {
1842                        // Non-indentation tab behaves like blank.
1843                        self.skip_blank();
1844                    }
1845                }
1846
1847                ' ' => self.skip_blank(),
1848
1849                '\n' | '\r' => consume_linebreak(self),
1850
1851                c if is_bom(c)
1852                    && self.document_prefix_allowed
1853                    && self.flow_level == 0
1854                    && self.mark.col == 0 =>
1855                {
1856                    self.skip_bom();
1857                }
1858
1859                '#' => {
1860                    outcome.saw_comment = true;
1861                    let queued = self.consume_comment()?;
1862                    outcome.queued_comment |= queued;
1863
1864                    // Micro-opt: comment-only lines are common; consume the following line break here.
1865                    if matches!(self.input.look_ch(), '\n' | '\r') {
1866                        consume_linebreak(self);
1867                    }
1868                    return Ok(outcome);
1869                }
1870
1871                _ => break,
1872            }
1873        }
1874
1875        // If a plain scalar was interrupted by a comment, and the next line could
1876        // continue the scalar in block context, this is invalid.
1877        if let Some(err_mark) = self.interrupted_plain_by_comment.take() {
1878            // BS4K should only trigger when the continuation would start on the immediate next
1879            // line (no intervening empty/comment-only lines). A blank line resets the folding
1880            // opportunity and thus should not error.
1881            let is_immediate_next_line = self.mark.line == err_mark.line + 1;
1882
1883            // Optimization: do the cheap checks first; only then request extra lookahead / do deeper checks.
1884            if self.flow_level == 0
1885                && is_immediate_next_line
1886                && (self.mark.col as isize) > self.indent
1887            {
1888                // Ensure enough lookahead for:
1889                // - the checks below (peek/peek_nth)
1890                // - document indicator detection which needs 4 chars.
1891                self.input.lookahead(4);
1892
1893                if !self.input.next_is_z()
1894                    && !self.input.next_is_document_indicator()
1895                    && self.input.next_can_be_plain_scalar(false)
1896                {
1897                    return Err(ScanError::from_kind(
1898                        err_mark,
1899                        ErrorKind::CommentInterceptedScalar,
1900                    ));
1901                }
1902            }
1903        }
1904
1905        Ok(outcome)
1906    }
1907
1908    /// Skip over YAML whitespace (` `, `\n`, `\r`).
1909    ///
1910    /// The function returns after one separated comment, whether it was queued or ignored, so a
1911    /// caller can publish completed syntax before validating later input.
1912    ///
1913    /// # Errors
1914    /// This function returns an error if no whitespace was found.
1915    fn skip_yaml_whitespace(&mut self) -> Result<bool, ScanError> {
1916        let mut need_whitespace = true;
1917        loop {
1918            match self.input.look_ch() {
1919                ' ' => {
1920                    self.skip_blank();
1921
1922                    need_whitespace = false;
1923                }
1924                '\n' | '\r' => {
1925                    self.input.lookahead(2);
1926                    self.skip_linebreak();
1927                    if self.flow_level == 0 {
1928                        self.allow_simple_key();
1929                    }
1930                    need_whitespace = false;
1931                }
1932                '#' => {
1933                    if need_whitespace {
1934                        self.skip_comment()?;
1935                    } else {
1936                        self.consume_comment()?;
1937                        return Ok(true);
1938                    }
1939                }
1940                _ => break,
1941            }
1942        }
1943
1944        if need_whitespace {
1945            Err(self.scan_error(ErrorKind::ExpectedWhitespace))
1946        } else {
1947            Ok(false)
1948        }
1949    }
1950
1951    /// Skip YAML whitespace up to the end of the current line.
1952    ///
1953    /// # Panics
1954    /// Panics in debug builds if `skip_tabs` is [`SkipTabs::Result`].
1955    #[track_caller]
1956    fn skip_ws_to_eol(&mut self, skip_tabs: SkipTabs) -> Result<SkipTabs, ScanError> {
1957        debug_assert!(!matches!(skip_tabs, SkipTabs::Result(..)));
1958
1959        if !self.comments_possible {
1960            let (chars_consumed, result) = self.input.skip_ws_to_eol(skip_tabs);
1961            self.mark.col += chars_consumed;
1962            self.mark.offsets.chars += chars_consumed;
1963            self.mark.offsets.bytes = self.input.byte_offset();
1964            return result.map_err(|kind| self.scan_error(kind));
1965        }
1966
1967        let (chars_consumed, whitespace) = self.input.skip_ws_to_eol_blanks(skip_tabs);
1968        self.mark.col += chars_consumed;
1969        self.mark.offsets.chars += chars_consumed;
1970        self.mark.offsets.bytes = self.input.byte_offset();
1971
1972        if self.input.look_ch() != '#' {
1973            return Ok(whitespace);
1974        }
1975
1976        if !whitespace.found_tabs() && !whitespace.has_valid_yaml_ws() {
1977            return Err(self.scan_error(ErrorKind::CommentNotSeparated));
1978        }
1979
1980        self.consume_comment()?;
1981        Ok(whitespace)
1982    }
1983
1984    fn fetch_stream_start(&mut self) {
1985        let mark = self.mark;
1986        self.indent = -1;
1987        self.stream_start_produced = true;
1988        self.allow_simple_key();
1989        self.tokens
1990            .push_back(Token(Span::empty(mark), TokenType::StreamStart).into());
1991        self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0)));
1992    }
1993
1994    fn fetch_stream_end(&mut self) -> ScanResult {
1995        // force new line
1996        if self.mark.col != 0 {
1997            self.mark.col = 0;
1998            self.mark.line += 1;
1999        }
2000
2001        if let Some((mark, bracket)) = self.flow_markers.pop() {
2002            return Err(Self::unclosed_bracket(mark, bracket));
2003        }
2004
2005        // If the stream ended, we won't have more context. We can stall all the simple keys we
2006        // had. If one was required, however, that was an error and we must propagate it.
2007        for sk in &mut self.simple_keys {
2008            if sk.required && sk.possible {
2009                return Err(Self::simple_key_expected(sk.mark));
2010            }
2011            sk.possible = false;
2012        }
2013
2014        self.unroll_indent(-1);
2015        self.remove_simple_key()?;
2016        self.disallow_simple_key();
2017
2018        self.tokens
2019            .push_back(Token(Span::empty(self.mark), TokenType::StreamEnd).into());
2020        Ok(())
2021    }
2022
2023    fn fetch_directive(&mut self) -> ScanResult {
2024        self.unroll_indent(-1);
2025        self.remove_simple_key()?;
2026
2027        self.disallow_simple_key();
2028
2029        let token_index = self.tokens.len();
2030        let tok = self.scan_directive()?;
2031        self.insert_token(token_index, tok);
2032
2033        Ok(())
2034    }
2035
2036    fn scan_directive(&mut self) -> Result<Token<'input>, ScanError> {
2037        let start_mark = self.mark;
2038        self.skip_non_blank();
2039
2040        // Directives precede every event, so a document could otherwise make the scanner retain
2041        // memory proportional to the input before any event, node or scalar budget can apply.
2042        let mut budget = self.options.max_directive_bytes;
2043
2044        let name = self.scan_directive_name(&mut budget)?;
2045        let mut separated_comment = false;
2046        let tok = match name.as_ref() {
2047            "YAML" => self.scan_version_directive_value(&start_mark)?,
2048            "TAG" => self.scan_tag_directive_value(&start_mark, &mut budget)?,
2049            _ => {
2050                let mut params = Vec::new();
2051                while self.input.next_is_blank() {
2052                    let n_blanks = self.input.skip_while_blank();
2053                    self.mark.offsets.chars += n_blanks;
2054                    self.mark.col += n_blanks;
2055                    self.mark.offsets.bytes = self.input.byte_offset();
2056
2057                    if self.input.peek() == '#' {
2058                        separated_comment = true;
2059                        break;
2060                    }
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        if separated_comment {
2093            // The reserved-parameter loop already consumed the comment's separator.
2094            self.consume_comment()?;
2095        } else {
2096            self.skip_ws_to_eol(SkipTabs::Yes)?;
2097        }
2098
2099        if self.input.next_is_breakz() {
2100            self.input.lookahead(2);
2101            self.skip_linebreak();
2102            Ok(tok)
2103        } else {
2104            Err(ScanError::from_kind(
2105                start_mark,
2106                ErrorKind::InvalidDirectiveTerminator,
2107            ))
2108        }
2109    }
2110
2111    fn scan_version_directive_value(&mut self, mark: &Marker) -> Result<Token<'input>, ScanError> {
2112        let n_blanks = self.input.skip_while_blank();
2113        self.mark.offsets.chars += n_blanks;
2114        self.mark.col += n_blanks;
2115        self.mark.offsets.bytes = self.input.byte_offset();
2116
2117        let major = self.scan_version_directive_number(mark)?;
2118
2119        if self.input.peek() != '.' {
2120            return Err(ScanError::from_kind(
2121                *mark,
2122                ErrorKind::MissingYamlVersionSeparator,
2123            ));
2124        }
2125        self.skip_non_blank();
2126
2127        let minor = self.scan_version_directive_number(mark)?;
2128
2129        Ok(Token(
2130            Span::new(*mark, self.mark),
2131            TokenType::VersionDirective(major, minor),
2132        ))
2133    }
2134
2135    /// Build the error reported when a directive outgrows [`Options::max_directive_bytes`].
2136    fn directive_byte_limit(&self, start_mark: Marker) -> ScanError {
2137        ScanError::from_kind(
2138            start_mark,
2139            ErrorKind::DirectiveByteLimitExceeded {
2140                limit: self.options.max_directive_bytes,
2141            },
2142        )
2143    }
2144
2145    /// Spend source bytes from a directive-retention budget before growing owned storage.
2146    fn spend_directive_bytes(
2147        &self,
2148        budget: &mut usize,
2149        bytes: usize,
2150        start_mark: Marker,
2151    ) -> ScanResult {
2152        let Some(remaining) = budget.checked_sub(bytes) else {
2153            return Err(self.directive_byte_limit(start_mark));
2154        };
2155        *budget = remaining;
2156        Ok(())
2157    }
2158
2159    /// Fetch a run of [`is_yaml_non_space`] characters into `out`, spending `budget` bytes.
2160    ///
2161    /// This mirrors [`crate::input::Input::fetch_while_is_yaml_non_space`] but stops as soon as
2162    /// the run would outgrow `budget`, returning [`None`]. Checking before each push is what keeps
2163    /// the allocation bounded: a limit applied after the fetch would already have grown `out` to
2164    /// the size of the run.
2165    fn fetch_directive_word(&mut self, out: &mut String, budget: &mut usize) -> Option<usize> {
2166        let mut n_chars = 0;
2167
2168        loop {
2169            let c = self.input.look_ch();
2170            if !is_yaml_non_space(c) || is_z(c) {
2171                break;
2172            }
2173
2174            let len = c.len_utf8();
2175            if len > *budget {
2176                return None;
2177            }
2178            *budget -= len;
2179
2180            out.push(c);
2181            self.input.skip();
2182            n_chars += 1;
2183        }
2184
2185        Some(n_chars)
2186    }
2187
2188    fn scan_directive_name(&mut self, budget: &mut usize) -> Result<String, ScanError> {
2189        let start_mark = self.mark;
2190        let mut string = String::new();
2191
2192        let Some(n_chars) = self.fetch_directive_word(&mut string, budget) else {
2193            return Err(self.directive_byte_limit(start_mark));
2194        };
2195        self.mark.offsets.chars += n_chars;
2196        self.mark.col += n_chars;
2197        self.mark.offsets.bytes = self.input.byte_offset();
2198
2199        if string.is_empty() {
2200            return Err(ScanError::from_kind(
2201                start_mark,
2202                ErrorKind::MissingDirectiveName,
2203            ));
2204        }
2205
2206        if !is_blank_or_breakz(self.input.peek()) {
2207            return Err(ScanError::from_kind(
2208                start_mark,
2209                ErrorKind::InvalidDirectiveName,
2210            ));
2211        }
2212
2213        Ok(string)
2214    }
2215
2216    fn scan_version_directive_number(&mut self, mark: &Marker) -> Result<u32, ScanError> {
2217        let mut val = 0u32;
2218        let mut length = 0usize;
2219        while let Some(digit) = self.input.look_ch().to_digit(10) {
2220            if length + 1 > 9 {
2221                return Err(ScanError::from_kind(*mark, ErrorKind::YamlVersionTooLong));
2222            }
2223            length += 1;
2224            val = val * 10 + digit;
2225            self.skip_non_blank();
2226        }
2227
2228        if length == 0 {
2229            return Err(ScanError::from_kind(*mark, ErrorKind::MissingYamlVersion));
2230        }
2231
2232        Ok(val)
2233    }
2234
2235    fn scan_tag_directive_value(
2236        &mut self,
2237        mark: &Marker,
2238        budget: &mut usize,
2239    ) -> Result<Token<'input>, ScanError> {
2240        let n_blanks = self.input.skip_while_blank();
2241        self.mark.offsets.chars += n_blanks;
2242        self.mark.col += n_blanks;
2243        self.mark.offsets.bytes = self.input.byte_offset();
2244        self.spend_directive_bytes(budget, n_blanks, *mark)?;
2245
2246        let handle = self.scan_tag_handle_directive_cow(mark, budget)?;
2247
2248        let n_blanks = self.input.skip_while_blank();
2249        self.mark.offsets.chars += n_blanks;
2250        self.mark.col += n_blanks;
2251        self.mark.offsets.bytes = self.input.byte_offset();
2252        self.spend_directive_bytes(budget, n_blanks, *mark)?;
2253
2254        let prefix = self.scan_tag_prefix_directive_cow(mark, budget)?;
2255
2256        self.input.lookahead(1);
2257
2258        if self.input.next_is_blank_or_breakz() {
2259            Ok(Token(
2260                Span::new(*mark, self.mark),
2261                TokenType::TagDirective(handle, prefix),
2262            ))
2263        } else {
2264            Err(ScanError::from_kind(
2265                *mark,
2266                ErrorKind::InvalidTagDirectiveTerminator,
2267            ))
2268        }
2269    }
2270
2271    fn fetch_tag(&mut self) -> ScanResult {
2272        self.save_simple_key();
2273        self.disallow_simple_key();
2274
2275        let tok = self.scan_tag()?;
2276        self.tokens.push_back(tok.into());
2277        Ok(())
2278    }
2279
2280    fn scan_tag(&mut self) -> Result<Token<'input>, ScanError> {
2281        let start_mark = self.mark;
2282
2283        // Check if the tag is in the canonical form (verbatim).
2284        self.input.lookahead(2);
2285
2286        // If byte_offset is not available, use the original owned-only path.
2287        if self.input.byte_offset().is_none() {
2288            return self.scan_tag_owned(&start_mark);
2289        }
2290
2291        let (handle, suffix): (Cow<'input, str>, Cow<'input, str>) = if self
2292            .input
2293            .nth_char_is(1, '<')
2294        {
2295            // Verbatim tags always need owned strings (URI escapes).
2296            let suffix = self.scan_verbatim_tag(&start_mark)?;
2297            (Cow::Owned(String::new()), Cow::Owned(suffix))
2298        } else {
2299            // The tag has either the '!suffix' or the '!handle!suffix'
2300            let handle = self.scan_tag_handle_cow(&start_mark)?;
2301            // Check if it is, indeed, handle.
2302            if handle.len() >= 2 && handle.starts_with('!') && handle.ends_with('!') {
2303                // A tag handle starting with "!!" is a secondary tag handle.
2304                let suffix = self.scan_tag_shorthand_suffix_cow(&start_mark, true)?;
2305                (handle, suffix)
2306            } else {
2307                // Not a real handle, it's part of the suffix.
2308                // E.g., "!foo" -> handle="!", suffix="foo"
2309                // The "handle" we scanned is actually "!" + suffix_part1.
2310                // We need to also scan any remaining suffix characters.
2311                let remaining_suffix = self.scan_tag_shorthand_suffix_cow(&start_mark, false)?;
2312
2313                let suffix = self.combine_local_tag_suffix(&start_mark, handle, remaining_suffix);
2314
2315                // A special case: the '!' tag.  Set the handle to '' and the
2316                // suffix to '!'.
2317                if suffix.is_empty() {
2318                    (Cow::Borrowed(""), Cow::Borrowed("!"))
2319                } else {
2320                    (Cow::Borrowed("!"), suffix)
2321                }
2322            }
2323        };
2324
2325        if is_blank_or_breakz(self.input.look_ch())
2326            || (self.flow_level > 0 && matches!(self.input.peek(), ',' | ']' | '}'))
2327        {
2328            // YAML example 7.2 allows a tag to annotate an empty scalar when a separator or flow
2329            // delimiter follows.
2330            Ok(Token(
2331                Span::new(start_mark, self.mark),
2332                TokenType::Tag(handle, suffix),
2333            ))
2334        } else {
2335            Err(ScanError::from_kind(
2336                start_mark,
2337                ErrorKind::InvalidTagTerminator,
2338            ))
2339        }
2340    }
2341
2342    fn combine_local_tag_suffix(
2343        &self,
2344        start_mark: &Marker,
2345        handle: Cow<'input, str>,
2346        remaining: Cow<'input, str>,
2347    ) -> Cow<'input, str> {
2348        if handle.len() == 1 {
2349            return remaining;
2350        }
2351
2352        match (handle, remaining) {
2353            (Cow::Borrowed(handle), Cow::Borrowed(remaining)) => {
2354                if remaining.is_empty() {
2355                    return Cow::Borrowed(&handle[1..]);
2356                }
2357
2358                let borrowed = start_mark
2359                    .byte_offset()
2360                    .and_then(|start| start.checked_add(1))
2361                    .zip(self.input.byte_offset())
2362                    .and_then(|(start, end)| self.try_borrow_slice(start, end));
2363                borrowed.map_or_else(
2364                    || {
2365                        let mut combined =
2366                            String::with_capacity(handle.len() - 1 + remaining.len());
2367                        combined.push_str(&handle[1..]);
2368                        combined.push_str(remaining);
2369                        Cow::Owned(combined)
2370                    },
2371                    Cow::Borrowed,
2372                )
2373            }
2374            (Cow::Borrowed(handle), Cow::Owned(mut remaining)) => {
2375                remaining.reserve(handle.len() - 1);
2376                remaining.insert_str(0, &handle[1..]);
2377                Cow::Owned(remaining)
2378            }
2379            (Cow::Owned(mut handle), remaining) => {
2380                handle.remove(0);
2381                handle.push_str(&remaining);
2382                Cow::Owned(handle)
2383            }
2384        }
2385    }
2386
2387    /// Original owned-only tag scanning path for inputs without `byte_offset` support.
2388    fn scan_tag_owned(&mut self, start_mark: &Marker) -> Result<Token<'input>, ScanError> {
2389        let mut handle = String::new();
2390        let mut suffix;
2391
2392        if self.input.nth_char_is(1, '<') {
2393            suffix = self.scan_verbatim_tag(start_mark)?;
2394        } else {
2395            // The tag has either the '!suffix' or the '!handle!suffix'
2396            handle = self.scan_tag_handle(false, start_mark)?;
2397            // Check if it is, indeed, handle.
2398            if handle.len() >= 2 && handle.starts_with('!') && handle.ends_with('!') {
2399                // A tag handle starting with "!!" is a secondary tag handle.
2400                let is_secondary_handle = handle == "!!";
2401                suffix =
2402                    self.scan_tag_shorthand_suffix(false, is_secondary_handle, "", start_mark)?;
2403            } else {
2404                suffix = self.scan_tag_shorthand_suffix(false, false, &handle, start_mark)?;
2405                "!".clone_into(&mut handle);
2406                // A special case: the '!' tag.  Set the handle to '' and the
2407                // suffix to '!'.
2408                if suffix.is_empty() {
2409                    handle.clear();
2410                    "!".clone_into(&mut suffix);
2411                }
2412            }
2413        }
2414
2415        if is_blank_or_breakz(self.input.look_ch())
2416            || (self.flow_level > 0 && matches!(self.input.peek(), ',' | ']' | '}'))
2417        {
2418            // YAML example 7.2 allows a tag to annotate an empty scalar when a separator or flow
2419            // delimiter follows.
2420            Ok(Token(
2421                Span::new(*start_mark, self.mark),
2422                TokenType::Tag(handle.into(), suffix.into()),
2423            ))
2424        } else {
2425            Err(ScanError::from_kind(
2426                *start_mark,
2427                ErrorKind::InvalidTagTerminator,
2428            ))
2429        }
2430    }
2431
2432    /// Scan a tag handle as a `Cow<str>`, borrowing when possible.
2433    ///
2434    /// Tag handles are of the form `!`, `!!`, or `!name!` where name is ASCII alphanumeric.
2435    /// Since they contain no escape sequences, they can always be borrowed from `StrInput`.
2436    fn scan_tag_handle_cow(&mut self, mark: &Marker) -> Result<Cow<'input, str>, ScanError> {
2437        let Some(start) = self.input.byte_offset() else {
2438            return Ok(Cow::Owned(self.scan_tag_handle(false, mark)?));
2439        };
2440
2441        if self.input.look_ch() != '!' {
2442            return Err(ScanError::from_kind(*mark, ErrorKind::ExpectedTagBang));
2443        }
2444
2445        // Consume the leading '!'.
2446        self.skip_non_blank();
2447
2448        // Consume ns-word-char (ASCII alphanumeric, '_' or '-') characters.
2449        self.input.lookahead(1);
2450        while self.input.next_is_alpha() {
2451            self.skip_non_blank();
2452            self.input.lookahead(1);
2453        }
2454
2455        // Optional trailing '!'.
2456        if self.input.peek() == '!' {
2457            self.skip_non_blank();
2458        }
2459
2460        let Some(end) = self.input.byte_offset() else {
2461            return Ok(Cow::Owned(self.scan_tag_handle(false, mark)?));
2462        };
2463
2464        if let Some(slice) = self.try_borrow_slice(start, end) {
2465            Ok(Cow::Borrowed(slice))
2466        } else {
2467            let slice = self
2468                .input
2469                .slice_bytes(start, end)
2470                .ok_or_else(|| ScanError::from_kind(*mark, ErrorKind::InputSlicingUnavailable))?;
2471            Ok(Cow::Owned(slice.to_owned()))
2472        }
2473    }
2474
2475    /// Scan a tag shorthand suffix as a `Cow<str>`, borrowing when possible.
2476    ///
2477    /// The suffix can be borrowed only if no `%` URI escape sequences are present.
2478    fn scan_tag_shorthand_suffix_cow(
2479        &mut self,
2480        mark: &Marker,
2481        require_non_empty: bool,
2482    ) -> Result<Cow<'input, str>, ScanError> {
2483        let Some(start) = self.input.byte_offset() else {
2484            return Ok(Cow::Owned(
2485                self.scan_tag_shorthand_suffix(false, false, "", mark)?,
2486            ));
2487        };
2488
2489        // Scan tag characters, checking for URI escapes.
2490        while is_tag_char(self.input.look_ch()) {
2491            if self.input.peek() == '%' {
2492                // URI escape found - must decode, so fall back to owned path.
2493                let current = self
2494                    .input
2495                    .byte_offset()
2496                    .expect("byte_offset() must remain available once enabled");
2497                let mut out = if let Some(slice) = self.input.slice_bytes(start, current) {
2498                    slice.to_owned()
2499                } else {
2500                    String::new()
2501                };
2502
2503                // Continue scanning with owned buffer.
2504                while is_tag_char(self.input.look_ch()) {
2505                    if self.input.peek() == '%' {
2506                        out.push(self.scan_uri_escapes(mark)?);
2507                    } else {
2508                        out.push(self.input.peek());
2509                        self.skip_non_blank();
2510                    }
2511                }
2512                return Ok(Cow::Owned(out));
2513            }
2514            self.skip_non_blank();
2515        }
2516
2517        let Some(end) = self.input.byte_offset() else {
2518            return Ok(Cow::Owned(
2519                self.scan_tag_shorthand_suffix(false, false, "", mark)?,
2520            ));
2521        };
2522
2523        if require_non_empty && start == end {
2524            return Err(ScanError::from_kind(*mark, ErrorKind::MissingTagUri));
2525        }
2526
2527        if let Some(slice) = self.try_borrow_slice(start, end) {
2528            Ok(Cow::Borrowed(slice))
2529        } else {
2530            let slice = self
2531                .input
2532                .slice_bytes(start, end)
2533                .ok_or_else(|| ScanError::from_kind(*mark, ErrorKind::InputSlicingUnavailable))?;
2534            Ok(Cow::Owned(slice.to_owned()))
2535        }
2536    }
2537
2538    fn scan_tag_handle(&mut self, directive: bool, mark: &Marker) -> Result<String, ScanError> {
2539        let mut string = String::new();
2540        if self.input.look_ch() != '!' {
2541            return Err(ScanError::from_kind(*mark, ErrorKind::ExpectedTagBang));
2542        }
2543
2544        string.push(self.input.peek());
2545        self.skip_non_blank();
2546
2547        let n_chars = self.input.fetch_while_is_alpha(&mut string);
2548        self.mark.offsets.chars += n_chars;
2549        self.mark.col += n_chars;
2550        self.mark.offsets.bytes = self.input.byte_offset();
2551
2552        // Check if the trailing character is '!' and copy it.
2553        if self.input.peek() == '!' {
2554            string.push(self.input.peek());
2555            self.skip_non_blank();
2556        } else if directive && string != "!" {
2557            // It's either the '!' tag or not really a tag handle.  If it's a %TAG
2558            // directive, it's an error.  If it's a tag token, it must be a part of
2559            // URI.
2560            return Err(ScanError::from_kind(
2561                *mark,
2562                ErrorKind::ExpectedTagDirectiveBang,
2563            ));
2564        }
2565        Ok(string)
2566    }
2567
2568    /// Scan for a verbatim tag.
2569    ///
2570    /// The prefixing `!<` must _not_ have been skipped.
2571    fn scan_verbatim_tag(&mut self, start_mark: &Marker) -> Result<String, ScanError> {
2572        // Eat `!<`
2573        self.skip_non_blank();
2574        self.skip_non_blank();
2575
2576        let mut string = String::new();
2577        while is_uri_char(self.input.look_ch()) {
2578            if self.input.peek() == '%' {
2579                string.push(self.scan_uri_escapes(start_mark)?);
2580            } else {
2581                string.push(self.input.peek());
2582                self.skip_non_blank();
2583            }
2584        }
2585
2586        if string.is_empty() {
2587            return Err(ScanError::from_kind(*start_mark, ErrorKind::MissingTagUri));
2588        }
2589
2590        if self.input.peek() != '>' {
2591            return Err(ScanError::from_kind(
2592                *start_mark,
2593                ErrorKind::UnclosedVerbatimTag,
2594            ));
2595        }
2596        self.skip_non_blank();
2597
2598        Ok(string)
2599    }
2600
2601    fn scan_tag_shorthand_suffix(
2602        &mut self,
2603        _directive: bool,
2604        _is_secondary: bool,
2605        head: &str,
2606        mark: &Marker,
2607    ) -> Result<String, ScanError> {
2608        let mut length = head.len();
2609        let mut string = String::new();
2610
2611        // Copy the head if needed.
2612        // Note that we don't copy the leading '!' character.
2613        if length > 1 {
2614            string.extend(head.chars().skip(1));
2615        }
2616
2617        while is_tag_char(self.input.look_ch()) {
2618            // Check if it is a URI-escape sequence.
2619            if self.input.peek() == '%' {
2620                string.push(self.scan_uri_escapes(mark)?);
2621            } else {
2622                string.push(self.input.peek());
2623                self.skip_non_blank();
2624            }
2625
2626            length += 1;
2627        }
2628
2629        if length == 0 {
2630            return Err(ScanError::from_kind(*mark, ErrorKind::MissingTagUri));
2631        }
2632
2633        Ok(string)
2634    }
2635
2636    fn scan_uri_escapes(&mut self, mark: &Marker) -> Result<char, ScanError> {
2637        self.scan_uri_escapes_with_budget(mark, None)
2638    }
2639
2640    fn scan_uri_escapes_with_budget(
2641        &mut self,
2642        mark: &Marker,
2643        mut directive_budget: Option<&mut usize>,
2644    ) -> Result<char, ScanError> {
2645        let mut width = 0usize;
2646        let mut bytes = [0u8; 4];
2647        let mut bytes_len = 0usize;
2648        loop {
2649            self.input.lookahead(3);
2650
2651            let c = self.input.peek_nth(1);
2652            let nc = self.input.peek_nth(2);
2653
2654            if !(self.input.peek() == '%' && is_hex(c) && is_hex(nc)) {
2655                return Err(ScanError::from_kind(*mark, ErrorKind::InvalidTagEscape));
2656            }
2657
2658            if let Some(budget) = directive_budget.as_deref_mut() {
2659                self.spend_directive_bytes(budget, 3, *mark)?;
2660            }
2661
2662            let byte = u8::try_from((as_hex(c) << 4) + as_hex(nc))
2663                .expect("two hex nibbles always fit in a byte");
2664            if width == 0 {
2665                width = match byte {
2666                    _ if byte & 0x80 == 0x00 => 1,
2667                    _ if byte & 0xE0 == 0xC0 => 2,
2668                    _ if byte & 0xF0 == 0xE0 => 3,
2669                    _ if byte & 0xF8 == 0xF0 => 4,
2670                    _ => {
2671                        return Err(ScanError::from_kind(
2672                            *mark,
2673                            ErrorKind::InvalidTagUtf8LeadingByte,
2674                        ));
2675                    }
2676                };
2677            } else if byte & 0xc0 != 0x80 {
2678                return Err(ScanError::from_kind(
2679                    *mark,
2680                    ErrorKind::InvalidTagUtf8TrailingByte,
2681                ));
2682            }
2683
2684            bytes[bytes_len] = byte;
2685            bytes_len += 1;
2686
2687            self.skip_n_non_blank(3);
2688
2689            width -= 1;
2690            if width == 0 {
2691                break;
2692            }
2693        }
2694
2695        let s = core::str::from_utf8(&bytes[..bytes_len])
2696            .map_err(|_| ScanError::from_kind(*mark, ErrorKind::InvalidTagUtf8))?;
2697
2698        let Some(ch) = s.chars().next() else {
2699            unreachable!("a validated URI escape cannot decode to an empty string")
2700        };
2701        Ok(ch)
2702    }
2703
2704    fn fetch_anchor(&mut self, alias: bool) -> ScanResult {
2705        self.save_simple_key();
2706        self.disallow_simple_key();
2707
2708        let tok = self.scan_anchor(alias)?;
2709
2710        self.tokens.push_back(tok.into());
2711
2712        Ok(())
2713    }
2714
2715    fn scan_anchor(&mut self, alias: bool) -> Result<Token<'input>, ScanError> {
2716        let start_mark = self.mark;
2717
2718        // Skip `&` / `*`.
2719        self.skip_non_blank();
2720
2721        // Borrow from input when possible.
2722        if let Some(start) = self.input.byte_offset() {
2723            while is_anchor_char(self.input.look_ch()) {
2724                self.skip_non_blank();
2725            }
2726
2727            let end = self
2728                .input
2729                .byte_offset()
2730                .expect("byte_offset() must remain available once enabled");
2731
2732            if start == end {
2733                return Err(ScanError::from_kind(
2734                    start_mark,
2735                    ErrorKind::MissingAnchorOrAliasName,
2736                ));
2737            }
2738
2739            let cow = if let Some(slice) = self.try_borrow_slice(start, end) {
2740                Cow::Borrowed(slice)
2741            } else if let Some(slice) = self.input.slice_bytes(start, end) {
2742                Cow::Owned(slice.to_owned())
2743            } else {
2744                return Err(ScanError::from_kind(
2745                    start_mark,
2746                    ErrorKind::InputSlicingUnavailable,
2747                ));
2748            };
2749
2750            let tok = if alias {
2751                TokenType::Alias(cow)
2752            } else {
2753                TokenType::Anchor(cow)
2754            };
2755            return Ok(Token(Span::new(start_mark, self.mark), tok));
2756        }
2757
2758        let mut string = String::new();
2759        while is_anchor_char(self.input.look_ch()) {
2760            string.push(self.input.peek());
2761            self.skip_non_blank();
2762        }
2763
2764        if string.is_empty() {
2765            return Err(ScanError::from_kind(
2766                start_mark,
2767                ErrorKind::MissingAnchorOrAliasName,
2768            ));
2769        }
2770
2771        let tok = if alias {
2772            TokenType::Alias(string.into())
2773        } else {
2774            TokenType::Anchor(string.into())
2775        };
2776        Ok(Token(Span::new(start_mark, self.mark), tok))
2777    }
2778
2779    fn fetch_flow_collection_start(&mut self, tok: TokenType<'input>) -> ScanResult {
2780        // The indicators '[' and '{' may start a simple key.
2781        self.save_simple_key();
2782
2783        let start_mark = self.mark;
2784        let indicator = self.input.peek();
2785        self.flow_markers.push((start_mark, indicator));
2786
2787        self.roll_one_col_indent();
2788        self.increase_flow_level()?;
2789
2790        self.allow_simple_key();
2791
2792        self.skip_non_blank();
2793        let end_mark = self.mark;
2794
2795        if tok == TokenType::FlowMappingStart {
2796            self.flow_mapping_started.push(true);
2797        } else {
2798            self.flow_mapping_started.push(false);
2799            self.implicit_flow_mapping_states
2800                .push(ImplicitMappingState::Possible);
2801        }
2802
2803        let token_index = self.tokens.len();
2804        self.skip_ws_to_eol(SkipTabs::Yes)?;
2805
2806        self.insert_token(token_index, Token(Span::new(start_mark, end_mark), tok));
2807        Ok(())
2808    }
2809
2810    fn fetch_flow_collection_end(
2811        &mut self,
2812        tok: TokenType<'input>,
2813        expected_open: char,
2814        actual_close: char,
2815    ) -> ScanResult {
2816        // A closing bracket without a corresponding opening is invalid YAML.
2817        if self.flow_level == 0 {
2818            return Err(self.scan_error(ErrorKind::MisplacedFlowCollectionEnd));
2819        }
2820
2821        let Some((open_mark, open_ch)) = self.flow_markers.pop() else {
2822            return Err(self.scan_error(ErrorKind::MisplacedFlowCollectionEnd));
2823        };
2824
2825        if open_ch != expected_open {
2826            return Err(ScanError::from_kind(
2827                open_mark,
2828                ErrorKind::MismatchedFlowCollectionEnd {
2829                    open: open_ch,
2830                    close: actual_close,
2831                },
2832            ));
2833        }
2834
2835        let flow_level = self.flow_level;
2836
2837        self.remove_simple_key()?;
2838
2839        if matches!(tok, TokenType::FlowSequenceEnd) {
2840            self.end_implicit_mapping(self.mark, flow_level);
2841            // We are out exiting the flow sequence, nesting goes down 1 level.
2842            self.implicit_flow_mapping_states.pop();
2843        }
2844        self.flow_mapping_started.pop();
2845
2846        self.decrease_flow_level();
2847
2848        self.disallow_simple_key();
2849
2850        let start_mark = self.mark;
2851        self.skip_non_blank();
2852        let end_mark = self.mark;
2853        let token_index = self.tokens.len();
2854        self.skip_ws_to_eol(SkipTabs::Yes)?;
2855
2856        // A flow collection within a flow mapping can be a key. In that case, the value may be
2857        // adjacent to the `:`.
2858        // ```yaml
2859        // - [ {a: b}:value ]
2860        // ```
2861        if self.flow_level > 0 {
2862            self.adjacent_value_allowed_at = self.mark.index();
2863        }
2864
2865        self.insert_token(token_index, Token(Span::new(start_mark, end_mark), tok));
2866        Ok(())
2867    }
2868
2869    /// Push the `FlowEntry` token and skip over the `,`.
2870    fn fetch_flow_entry(&mut self) -> ScanResult {
2871        self.remove_simple_key()?;
2872        self.allow_simple_key();
2873
2874        self.end_implicit_mapping(self.mark, self.flow_level);
2875        if self.current_flow_collection_is_sequence() {
2876            self.set_current_flow_mapping_started(false);
2877        }
2878
2879        let start_mark = self.mark;
2880        self.skip_non_blank();
2881        let end_mark = self.mark;
2882        let token_index = self.tokens.len();
2883        self.skip_ws_to_eol(SkipTabs::Yes)?;
2884
2885        self.insert_token(
2886            token_index,
2887            Token(Span::new(start_mark, end_mark), TokenType::FlowEntry),
2888        );
2889        Ok(())
2890    }
2891
2892    fn increase_flow_level(&mut self) -> ScanResult {
2893        if self.flow_level >= self.options.flow_nesting_limit {
2894            return Err(self.scan_error(ErrorKind::RecursionLimitExceeded));
2895        }
2896
2897        self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0)));
2898        self.flow_level += 1;
2899        Ok(())
2900    }
2901
2902    fn decrease_flow_level(&mut self) {
2903        if self.flow_level > 0 {
2904            self.flow_level -= 1;
2905            self.simple_keys.pop().unwrap();
2906        }
2907    }
2908
2909    /// Push the `Block*` token(s) and skip over the `-`.
2910    ///
2911    /// Add an indentation level and push a `BlockSequenceStart` token if needed, then push a
2912    /// `BlockEntry` token.
2913    /// This function only skips over the `-` and does not fetch the entry value.
2914    fn fetch_block_entry(&mut self) -> ScanResult {
2915        if self.flow_level > 0 {
2916            // - * only allowed in block
2917            return Err(self.scan_error(ErrorKind::BlockEntryInFlowCollection));
2918        }
2919        // Check if we are allowed to start a new entry.
2920        if !self.simple_key_allowed {
2921            return Err(self.scan_error(ErrorKind::BlockSequenceEntryNotAllowed));
2922        }
2923
2924        // Skip over the `-`.
2925        let mark = self.mark;
2926        self.skip_non_blank();
2927
2928        // generate BLOCK-SEQUENCE-START if indented
2929        self.roll_indent(mark.col, None, TokenType::BlockSequenceStart, mark)?;
2930        let token_index = self.tokens.len();
2931        let found_tabs = self.skip_ws_to_eol(SkipTabs::Yes)?.found_tabs();
2932        self.input.lookahead(2);
2933        if found_tabs && self.input.next_char_is('-') && is_blank_or_breakz(self.input.peek_nth(1))
2934        {
2935            return Err(self.scan_error(ErrorKind::InvalidBlockEntryWhitespace));
2936        }
2937
2938        self.skip_ws_to_eol(SkipTabs::No)?;
2939        self.input.lookahead(1);
2940        if self.input.next_is_break() || self.input.next_is_flow() {
2941            self.roll_one_col_indent();
2942        }
2943
2944        self.remove_simple_key()?;
2945        self.allow_simple_key();
2946
2947        self.insert_token(
2948            token_index,
2949            Token(Span::empty(self.mark), TokenType::BlockEntry),
2950        );
2951
2952        Ok(())
2953    }
2954
2955    fn fetch_document_indicator(&mut self, t: TokenType<'input>) -> ScanResult {
2956        if let Some((mark, bracket)) = self.flow_markers.pop() {
2957            return Err(ScanError::from_kind(
2958                mark,
2959                ErrorKind::UnclosedFlowCollection { open: bracket },
2960            ));
2961        }
2962
2963        self.unroll_indent(-1);
2964        self.remove_simple_key()?;
2965        self.disallow_simple_key();
2966
2967        let mark = self.mark;
2968
2969        self.skip_n_non_blank(3);
2970
2971        self.document_prefix_allowed = matches!(t, TokenType::DocumentEnd);
2972        self.tokens
2973            .push_back(Token(Span::new(mark, self.mark), t).into());
2974        Ok(())
2975    }
2976
2977    fn fetch_block_scalar(&mut self, literal: bool) -> ScanResult {
2978        self.save_simple_key();
2979        self.allow_simple_key();
2980        let token_index = self.tokens.len();
2981        let tok = self.scan_block_scalar(literal)?;
2982
2983        self.insert_token(token_index, tok);
2984        Ok(())
2985    }
2986
2987    #[allow(clippy::too_many_lines)]
2988    fn scan_block_scalar(&mut self, literal: bool) -> Result<Token<'input>, ScanError> {
2989        let start_mark = self.mark;
2990        let mut chomping = Chomping::Clip;
2991        let mut increment: usize = 0;
2992        let mut indent: usize = 0;
2993        let mut trailing_blank: bool;
2994        let mut leading_blank: bool = false;
2995        let style = if literal {
2996            ScalarStyle::Literal
2997        } else {
2998            ScalarStyle::Folded
2999        };
3000
3001        let mut string = String::new();
3002        let mut leading_break = String::new();
3003        let mut trailing_breaks = String::new();
3004        let mut chomping_break = String::new();
3005
3006        // skip '|' or '>'
3007        self.skip_non_blank();
3008        self.unroll_non_block_indents();
3009
3010        if self.input.look_ch() == '+' || self.input.peek() == '-' {
3011            if self.input.peek() == '+' {
3012                chomping = Chomping::Keep;
3013            } else {
3014                chomping = Chomping::Strip;
3015            }
3016            self.skip_non_blank();
3017            self.input.lookahead(1);
3018            if self.input.next_is_digit() {
3019                if self.input.peek() == '0' {
3020                    return Err(ScanError::from_kind(
3021                        start_mark,
3022                        ErrorKind::ZeroBlockScalarIndent,
3023                    ));
3024                }
3025                increment = (self.input.peek() as usize) - ('0' as usize);
3026                self.skip_non_blank();
3027            }
3028        } else if self.input.next_is_digit() {
3029            if self.input.peek() == '0' {
3030                return Err(ScanError::from_kind(
3031                    start_mark,
3032                    ErrorKind::ZeroBlockScalarIndent,
3033                ));
3034            }
3035
3036            increment = (self.input.peek() as usize) - ('0' as usize);
3037            self.skip_non_blank();
3038            self.input.lookahead(1);
3039            if self.input.peek() == '+' || self.input.peek() == '-' {
3040                if self.input.peek() == '+' {
3041                    chomping = Chomping::Keep;
3042                } else {
3043                    chomping = Chomping::Strip;
3044                }
3045                self.skip_non_blank();
3046            }
3047        }
3048
3049        self.skip_ws_to_eol(SkipTabs::Yes)?;
3050
3051        // Check if we are at the end of the line.
3052        self.input.lookahead(1);
3053        self.ensure_current_char_is_printable()?;
3054        if !self.input.next_is_breakz() {
3055            return Err(ScanError::from_kind(
3056                start_mark,
3057                ErrorKind::InvalidBlockScalarHeader,
3058            ));
3059        }
3060
3061        if self.input.next_is_break() {
3062            self.input.lookahead(2);
3063            self.read_break(&mut chomping_break);
3064        }
3065
3066        self.ensure_current_char_is_printable()?;
3067
3068        if self.input.look_ch() == '\t' {
3069            return Err(ScanError::from_kind(
3070                start_mark,
3071                ErrorKind::TabAtBlockScalarStart,
3072            ));
3073        }
3074
3075        if increment > 0 {
3076            indent = if self.indent >= 0 {
3077                (self.indent + increment as isize) as usize
3078            } else {
3079                increment
3080            }
3081        }
3082
3083        // Scan the leading line breaks and determine the indentation level if needed.
3084        if indent == 0 {
3085            self.skip_block_scalar_first_line_indent(&mut indent, &mut trailing_breaks);
3086        } else {
3087            self.skip_block_scalar_indent(indent, &mut trailing_breaks);
3088        }
3089
3090        self.ensure_current_char_is_printable()?;
3091
3092        // We have an end-of-stream with no content, e.g.:
3093        // ```yaml
3094        // - |+
3095        // ```
3096        if self.input.next_is_z() {
3097            let contents = match chomping {
3098                // We strip trailing line breaks. Nothing remains.
3099                Chomping::Strip => String::new(),
3100                // There was no newline after the chomping indicator.
3101                _ if self.mark.line == start_mark.line() => String::new(),
3102                // With no content lines, the header break is not scalar content.
3103                Chomping::Clip => String::new(),
3104                // An indented whitespace-only line at EOF is an empty content line.
3105                Chomping::Keep if trailing_breaks.is_empty() && self.mark.col > 0 => chomping_break,
3106                // Keep actual empty content lines, if any, but not the header break.
3107                Chomping::Keep => trailing_breaks,
3108            };
3109
3110            return Ok(Token(
3111                Span::new(start_mark, self.mark),
3112                TokenType::Scalar(style, contents.into()),
3113            ));
3114        }
3115
3116        if self.mark.col < indent && (self.mark.col as isize) > self.indent {
3117            if self.indent < 0 && self.mark.col == 0 {
3118                self.input.lookahead(4);
3119                if self.input.next_is_document_start()
3120                    || self.input.next_is_document_end()
3121                    || self.input.peek() == '#'
3122                {
3123                    // At the root level, an explicit indentation indicator can still yield an
3124                    // empty scalar when the next line is a document marker or comment.
3125                    // In this case, the scalar is terminated rather than under-indented.
3126                } else {
3127                    return Err(self.scan_error(ErrorKind::InvalidBlockScalarIndent));
3128                }
3129            } else {
3130                return Err(self.scan_error(ErrorKind::InvalidBlockScalarIndent));
3131            }
3132        }
3133
3134        let mut line_buffer = String::with_capacity(100);
3135        let start_mark = self.mark;
3136        while self.mark.col == indent && !self.input.next_is_z() {
3137            self.ensure_current_char_is_printable()?;
3138
3139            if indent == 0 {
3140                self.input.lookahead(4);
3141                if self.input.next_is_document_indicator() {
3142                    break;
3143                }
3144            }
3145
3146            // We are at the first content character of a content line.
3147            trailing_blank = self.input.next_is_blank();
3148            if !literal && !leading_break.is_empty() && !leading_blank && !trailing_blank {
3149                string.push_str(&trailing_breaks);
3150                if trailing_breaks.is_empty() {
3151                    string.push(' ');
3152                }
3153            } else {
3154                string.push_str(&leading_break);
3155                string.push_str(&trailing_breaks);
3156            }
3157
3158            leading_break.clear();
3159            trailing_breaks.clear();
3160
3161            leading_blank = self.input.next_is_blank();
3162
3163            self.scan_block_scalar_content_line(&mut string, &mut line_buffer);
3164
3165            // break on EOF
3166            self.input.lookahead(2);
3167            if self.input.next_is_z() {
3168                break;
3169            }
3170
3171            self.ensure_current_char_is_printable()?;
3172
3173            self.read_break(&mut leading_break);
3174
3175            // Eat the following indentation spaces and line breaks.
3176            self.skip_block_scalar_indent(indent, &mut trailing_breaks);
3177        }
3178
3179        self.ensure_current_char_is_printable()?;
3180
3181        // Chomp the tail.
3182        if chomping != Chomping::Strip {
3183            string.push_str(&leading_break);
3184            // If we had reached an eof but the last character wasn't an end-of-line, check if the
3185            // last line was indented at least as the rest of the scalar, then we need to consider
3186            // there is a newline.
3187            if self.input.next_is_z() && self.mark.col >= indent.max(1) {
3188                string.push('\n');
3189            }
3190        }
3191
3192        if chomping == Chomping::Keep {
3193            string.push_str(&trailing_breaks);
3194        }
3195
3196        if let Some(character) = find_non_printable(&string) {
3197            return Err(ScanError::from_kind(
3198                start_mark,
3199                ErrorKind::UnexpectedCharacter { character },
3200            ));
3201        }
3202
3203        let span = if string.trim().is_empty() {
3204            Span::new(start_mark, self.mark)
3205        } else {
3206            Span::new(start_mark, self.mark).with_indent(Some(indent))
3207        };
3208
3209        Ok(Token(span, TokenType::Scalar(style, string.into())))
3210    }
3211
3212    /// Retrieve the contents of the line, parsing it as a block scalar.
3213    ///
3214    /// The contents will be appended to `string`. `line_buffer` is used as a temporary buffer to
3215    /// store bytes before pushing them to `string` and thus avoiding reallocating more than
3216    /// necessary. `line_buffer` is assumed to be empty upon calling this function. It will be
3217    /// `clear`ed before the end of the function.
3218    ///
3219    /// This function assumes the first character to read is the first content character in the
3220    /// line. This function does not consume the line break character(s) after the line.
3221    fn scan_block_scalar_content_line(&mut self, string: &mut String, line_buffer: &mut String) {
3222        // Start by evaluating characters in the buffer.
3223        while !self.input.buf_is_empty() && !self.input.next_is_breakz() {
3224            string.push(self.input.peek());
3225            // We may technically skip non-blank characters. However, the only distinction is
3226            // to determine what is leading whitespace and what is not. Here, we read the
3227            // contents of the line until either EOF or a line break. We know we will not read
3228            // `self.leading_whitespace` until the end of the line, where it will be reset.
3229            // This allows us to call a slightly less expensive function.
3230            self.skip_blank();
3231        }
3232
3233        // All characters that were in the buffer were consumed. We need to check if more
3234        // follow.
3235        if self.input.buf_is_empty() {
3236            // We will read all consecutive non-breakz characters. We push them into a
3237            // temporary buffer. The main difference with going through `self.buffer` is that
3238            // characters are appended here as their real size (1B for ASCII, or up to 4 bytes for
3239            // UTF-8). We can then use the internal `line_buffer` `Vec` to push data into `string`
3240            // (using `String::push_str`).
3241
3242            // line_buffer is empty at this point so we can compute n_chars here as well
3243            let mut n_chars = 0;
3244            debug_assert!(line_buffer.is_empty());
3245            while let Some(c) = self.input.raw_read_non_breakz_ch() {
3246                line_buffer.push(c);
3247                n_chars += 1;
3248            }
3249
3250            // We need to manually update our position; we haven't called a `skip` function.
3251            self.mark.col += n_chars;
3252            self.mark.offsets.chars += n_chars;
3253            self.mark.offsets.bytes = self.input.byte_offset();
3254
3255            // We can now append our bytes to our `string`.
3256            string.reserve(line_buffer.len());
3257            string.push_str(line_buffer);
3258            // This clears the _contents_ without touching the _capacity_.
3259            line_buffer.clear();
3260        }
3261    }
3262
3263    /// Skip the block scalar indentation and empty lines.
3264    fn skip_block_scalar_indent(&mut self, indent: usize, breaks: &mut String) {
3265        loop {
3266            // Consume all spaces. Tabs cannot be used as indentation.
3267            if indent < self.input.bufmaxlen().saturating_sub(2) {
3268                self.input.lookahead(self.input.bufmaxlen());
3269                while self.mark.col < indent && self.input.peek() == ' ' {
3270                    self.skip_blank();
3271                }
3272            } else {
3273                loop {
3274                    self.input.lookahead(self.input.bufmaxlen());
3275                    while !self.input.buf_is_empty()
3276                        && self.mark.col < indent
3277                        && self.input.peek() == ' '
3278                    {
3279                        self.skip_blank();
3280                    }
3281                    // If we reached our indent, we can break. We must also break if we have
3282                    // reached content or EOF; that is, the buffer is not empty and the next
3283                    // character is not a space.
3284                    if self.mark.col == indent
3285                        || (!self.input.buf_is_empty() && self.input.peek() != ' ')
3286                    {
3287                        break;
3288                    }
3289                }
3290                self.input.lookahead(2);
3291            }
3292
3293            // If our current line is empty, skip over the break and continue looping.
3294            if self.input.next_is_break() {
3295                self.read_break(breaks);
3296            } else {
3297                // Otherwise, we have a content line. Return control.
3298                break;
3299            }
3300        }
3301    }
3302
3303    /// Determine the indentation level for a block scalar from the first line of its contents.
3304    ///
3305    /// The function skips over whitespace-only lines and sets `indent` to the longest
3306    /// whitespace line that was encountered.
3307    fn skip_block_scalar_first_line_indent(&mut self, indent: &mut usize, breaks: &mut String) {
3308        let mut max_indent = 0;
3309        loop {
3310            // Consume all spaces. Tabs cannot be used as indentation.
3311            while self.input.look_ch() == ' ' {
3312                self.skip_blank();
3313            }
3314
3315            if self.mark.col > max_indent {
3316                max_indent = self.mark.col;
3317            }
3318
3319            if self.input.next_is_break() {
3320                // If our current line is empty, skip over the break and continue looping.
3321                self.input.lookahead(2);
3322                self.read_break(breaks);
3323            } else {
3324                // Otherwise, we have a content line. Return control.
3325                break;
3326            }
3327        }
3328
3329        // In case a YAML document looks like:
3330        // ```yaml
3331        // |
3332        // foo
3333        // bar
3334        // ```
3335        // We need to set the indent to 0 and not 1. In all other cases, the indent must be at
3336        // least 1. When in the above example, `self.indent` will be set to -1.
3337        *indent = max_indent.max((self.indent + 1) as usize);
3338    }
3339
3340    fn fetch_flow_scalar(&mut self, single: bool) -> ScanResult {
3341        self.save_simple_key();
3342        self.disallow_simple_key();
3343
3344        let token_index = self.tokens.len();
3345        let tok = self.scan_flow_scalar(single)?;
3346
3347        // From spec: To ensure JSON compatibility, if a key inside a flow mapping is JSON-like,
3348        // YAML allows the following value to be specified adjacent to the “:”.
3349        if self.skip_to_next_token()?.saw_comment {
3350            self.adjacent_value_allowed_at = usize::MAX;
3351        } else {
3352            self.adjacent_value_allowed_at = self.mark.index();
3353        }
3354
3355        self.insert_token(token_index, tok);
3356        Ok(())
3357    }
3358
3359    #[allow(clippy::too_many_lines)]
3360    fn scan_flow_scalar(&mut self, single: bool) -> Result<Token<'input>, ScanError> {
3361        let start_mark = self.mark;
3362
3363        // Output scalar contents.
3364        let mut buf = match self.input.byte_offset() {
3365            Some(off) => FlowScalarBuf::new_borrowed(off + self.input.peek().len_utf8()),
3366            None => FlowScalarBuf::new_owned(),
3367        };
3368
3369        // Scratch used to consume the *first* line break in a break run without emitting it.
3370        // (The first break folds to ' ' or to nothing depending on escaping rules.)
3371        let mut break_scratch = String::new();
3372
3373        /* Eat the left quote. */
3374        self.skip_non_blank();
3375
3376        loop {
3377            /* Check for a document indicator. */
3378            self.input.lookahead(4);
3379
3380            if self.mark.col == 0 && self.input.next_is_document_indicator() {
3381                return Err(ScanError::from_kind(
3382                    start_mark,
3383                    ErrorKind::DocumentIndicatorInQuotedScalar,
3384                ));
3385            }
3386
3387            if self.input.next_is_z() {
3388                return Err(ScanError::from_kind(
3389                    start_mark,
3390                    ErrorKind::UnclosedQuotedScalar,
3391                ));
3392            }
3393
3394            self.ensure_current_char_is_printable()?;
3395
3396            // Do not enforce block indentation inside quoted (flow) scalars.
3397            // YAML allows line breaks within quoted scalars.
3398            let mut leading_blanks = false;
3399            self.consume_flow_scalar_non_whitespace_chars(
3400                single,
3401                &mut buf,
3402                &mut leading_blanks,
3403                &start_mark,
3404            )?;
3405
3406            match self.input.look_ch() {
3407                '\'' if single => break,
3408                '"' if !single => break,
3409                _ => {}
3410            }
3411
3412            // --- Faster whitespace / line break handling (no temporary Strings) ---
3413            //
3414            // Instead of:
3415            //   - collecting blanks into `whitespaces` and then copying them
3416            //   - collecting breaks into `leading_break` / `trailing_breaks` and then copying
3417            //
3418            // We do:
3419            //   - append trailing blanks directly to `string`, remember where they started,
3420            //     and truncate them if a line break follows.
3421            //   - for line breaks: consume the first break into a scratch (discarded),
3422            //     append subsequent breaks directly to `string`.
3423            //
3424            // These flags replace temporary-string emptiness checks:
3425            //   has_leading_break  <=> !leading_break.is_empty()
3426            //   has_trailing_breaks <=> !trailing_breaks.is_empty()
3427            let mut trailing_ws_start: Option<usize> = None;
3428            let mut has_leading_break = false;
3429            let mut has_trailing_breaks = false;
3430
3431            // For the borrowed path: track the (byte) start of a pending whitespace run.
3432            let mut pending_ws_start: Option<usize> = None;
3433
3434            // Consume blank characters.
3435            while self.input.next_is_blank() || self.input.next_is_break() {
3436                if self.input.next_is_blank() {
3437                    // Consume a space or a tab character.
3438                    if leading_blanks {
3439                        if self.input.peek() == '\t' && (self.mark.col as isize) < self.indent {
3440                            return Err(self.scan_error(ErrorKind::TabInIndentation));
3441                        }
3442                        self.skip_blank();
3443                    } else {
3444                        // Append to output immediately; if a break appears next, we'll truncate.
3445                        match buf {
3446                            FlowScalarBuf::Owned(ref mut string) => {
3447                                if trailing_ws_start.is_none() {
3448                                    trailing_ws_start = Some(string.len());
3449                                }
3450                                string.push(self.input.peek());
3451                            }
3452                            FlowScalarBuf::Borrowed { .. } => {
3453                                if pending_ws_start.is_none() {
3454                                    pending_ws_start = self.input.byte_offset();
3455                                }
3456                            }
3457                        }
3458                        self.skip_blank();
3459
3460                        if let (FlowScalarBuf::Borrowed { .. }, Some(ws_start), Some(ws_end)) =
3461                            (&mut buf, pending_ws_start, self.input.byte_offset())
3462                        {
3463                            buf.note_pending_ws(ws_start, ws_end);
3464                        }
3465                    }
3466                } else {
3467                    self.input.lookahead(2);
3468
3469                    // Check if it is a first line break.
3470                    if leading_blanks {
3471                        // Second+ line break in a run: preserve it.
3472                        match buf {
3473                            FlowScalarBuf::Owned(ref mut string) => self.read_break(string),
3474                            FlowScalarBuf::Borrowed { .. } => {
3475                                self.promote_flow_scalar_buf_to_owned(&start_mark, &mut buf)?;
3476                                let Some(string) = buf.as_owned_mut() else {
3477                                    unreachable!()
3478                                };
3479                                self.read_break(string);
3480                            }
3481                        }
3482                        has_trailing_breaks = true;
3483                    } else {
3484                        // First break: drop any trailing blanks we appended, then consume the break.
3485                        if let Some(pos) = trailing_ws_start.take() {
3486                            if let FlowScalarBuf::Owned(ref mut string) = buf {
3487                                string.truncate(pos);
3488                            }
3489                        }
3490
3491                        if pending_ws_start.take().is_some() {
3492                            // Trailing blanks before a break are discarded => transformation.
3493                            if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3494                                self.promote_flow_scalar_buf_to_owned(&start_mark, &mut buf)?;
3495                            }
3496                            buf.discard_pending_ws();
3497                        } else {
3498                            buf.commit_pending_ws();
3499                        }
3500
3501                        break_scratch.clear();
3502                        self.read_break(&mut break_scratch);
3503                        // Keep `break_scratch` content (ignored) until next clear; no need to clear twice.
3504
3505                        has_leading_break = true;
3506                        leading_blanks = true;
3507                    }
3508                }
3509
3510                self.input.lookahead(1);
3511            }
3512
3513            // If we had a line break inside a quoted (flow) scalar, validate indentation
3514            // of the continuation line in block context.
3515            if leading_blanks && has_leading_break && self.flow_level == 0 {
3516                let next_ch = self.input.peek();
3517                let is_closing_quote = (single && next_ch == '\'') || (!single && next_ch == '"');
3518                if !is_closing_quote && (self.mark.col as isize) <= self.indent {
3519                    return Err(self.scan_error(ErrorKind::InvalidQuotedScalarIndent));
3520                }
3521            }
3522
3523            // Join the whitespace or fold line breaks.
3524            if leading_blanks {
3525                // Folding rule:
3526                //   if there was no leading break, preserve the pending whitespace already emitted
3527                //   if there was a leading break but no trailing breaks, fold to one space
3528                //   otherwise, preserve the trailing breaks already emitted
3529                if has_leading_break && !has_trailing_breaks {
3530                    match buf {
3531                        FlowScalarBuf::Owned(ref mut string) => string.push(' '),
3532                        FlowScalarBuf::Borrowed { .. } => {
3533                            self.promote_flow_scalar_buf_to_owned(&start_mark, &mut buf)?;
3534                            let Some(string) = buf.as_owned_mut() else {
3535                                unreachable!()
3536                            };
3537                            string.push(' ');
3538                        }
3539                    }
3540                }
3541            }
3542            // else: trailing blanks are already appended to `string`
3543        } // loop
3544
3545        // Eat the right quote.
3546        self.skip_non_blank();
3547        let end_mark = self.mark;
3548
3549        // Ensure there is no invalid trailing content.
3550        self.skip_ws_to_eol(SkipTabs::Yes)?;
3551        self.ensure_current_char_is_printable()?;
3552        match self.input.peek() {
3553            // These can be encountered in flow sequences or mappings.
3554            ',' | '}' | ']' if self.flow_level > 0 => {}
3555            // An end-of-line / end-of-stream is fine. No trailing content.
3556            c if is_breakz(c) => {}
3557            // ':' can be encountered if our scalar is a key.
3558            // Outside of flow contexts, keys cannot span multiple lines
3559            ':' if self.flow_level == 0 && start_mark.line == self.mark.line => {}
3560            // Inside a flow context, this is allowed.
3561            ':' if self.flow_level > 0 => {}
3562            _ => {
3563                let kind = if single {
3564                    ErrorKind::InvalidTrailingSingleQuotedScalar
3565                } else {
3566                    ErrorKind::InvalidTrailingDoubleQuotedScalar
3567                };
3568                return Err(self.scan_error(kind));
3569            }
3570        }
3571
3572        let style = if single {
3573            ScalarStyle::SingleQuoted
3574        } else {
3575            ScalarStyle::DoubleQuoted
3576        };
3577
3578        let contents = match buf {
3579            FlowScalarBuf::Owned(string) => Cow::Owned(string),
3580            FlowScalarBuf::Borrowed {
3581                start,
3582                mut end,
3583                pending_ws_start,
3584                pending_ws_end,
3585            } => {
3586                // If we ended after a whitespace run, it is part of the output (no break followed).
3587                if pending_ws_start.is_some() {
3588                    end = pending_ws_end;
3589                }
3590                if let Some(slice) = self.try_borrow_slice(start, end) {
3591                    Cow::Borrowed(slice)
3592                } else {
3593                    let slice = self.input.slice_bytes(start, end).ok_or_else(|| {
3594                        ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3595                    })?;
3596                    Cow::Owned(slice.to_owned())
3597                }
3598            }
3599        };
3600
3601        Ok(Token(
3602            Span::new(start_mark, end_mark),
3603            TokenType::Scalar(style, contents),
3604        ))
3605    }
3606
3607    /// Consume successive non-whitespace characters from a flow scalar.
3608    ///
3609    /// This function resolves escape sequences and stops upon encountering a whitespace, the end
3610    /// of the stream or the closing character for the scalar (`'` for single quoted scalars, `"`
3611    /// for double quoted scalars).
3612    ///
3613    /// # Errors
3614    /// Return an error if an invalid escape sequence is found.
3615    fn consume_flow_scalar_non_whitespace_chars(
3616        &mut self,
3617        single: bool,
3618        buf: &mut FlowScalarBuf,
3619        leading_blanks: &mut bool,
3620        start_mark: &Marker,
3621    ) -> Result<(), ScanError> {
3622        self.input.lookahead(2);
3623        while !is_blank_or_breakz(self.input.peek()) && is_printable(self.input.peek()) {
3624            match self.input.peek() {
3625                // Check for an escaped single quote.
3626                '\'' if self.input.peek_nth(1) == '\'' && single => {
3627                    if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3628                        buf.commit_pending_ws();
3629                        self.promote_flow_scalar_buf_to_owned(start_mark, buf)?;
3630                    }
3631                    let Some(string) = buf.as_owned_mut() else {
3632                        unreachable!()
3633                    };
3634                    string.push('\'');
3635                    self.skip_n_non_blank(2);
3636                }
3637                // Check for the right quote.
3638                '\'' if single => break,
3639                '"' if !single => break,
3640                // Check for an escaped line break.
3641                '\\' if !single && is_break(self.input.peek_nth(1)) => {
3642                    self.input.lookahead(3);
3643                    if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3644                        buf.commit_pending_ws();
3645                        self.promote_flow_scalar_buf_to_owned(start_mark, buf)?;
3646                    }
3647                    self.skip_non_blank();
3648                    self.skip_linebreak();
3649                    *leading_blanks = true;
3650                    break;
3651                }
3652                // Check for an escape sequence.
3653                '\\' if !single => {
3654                    if matches!(buf, FlowScalarBuf::Borrowed { .. }) {
3655                        buf.commit_pending_ws();
3656                        self.promote_flow_scalar_buf_to_owned(start_mark, buf)?;
3657                    }
3658                    let Some(string) = buf.as_owned_mut() else {
3659                        unreachable!()
3660                    };
3661                    string.push(self.resolve_flow_scalar_escape_sequence(start_mark)?);
3662                }
3663                c => {
3664                    match buf {
3665                        FlowScalarBuf::Owned(ref mut string) => {
3666                            string.push(c);
3667                        }
3668                        FlowScalarBuf::Borrowed { .. } => {
3669                            buf.commit_pending_ws();
3670                        }
3671                    }
3672                    self.skip_non_blank();
3673
3674                    if let Some(new_end) = self.input.byte_offset() {
3675                        if let FlowScalarBuf::Borrowed { end, .. } = buf {
3676                            *end = new_end;
3677                        }
3678                    }
3679                }
3680            }
3681            self.input.lookahead(2);
3682        }
3683        Ok(())
3684    }
3685
3686    /// Escape the sequence we encounter in a flow scalar.
3687    ///
3688    /// `self.input.peek()` must point to the `\` starting the escape sequence.
3689    ///
3690    /// # Errors
3691    /// Return an error if an invalid escape sequence is found.
3692    fn resolve_flow_scalar_escape_sequence(
3693        &mut self,
3694        start_mark: &Marker,
3695    ) -> Result<char, ScanError> {
3696        let mut code_length = 0usize;
3697        let mut ret = '\0';
3698
3699        match self.input.peek_nth(1) {
3700            '0' => ret = '\0',
3701            'a' => ret = '\x07',
3702            'b' => ret = '\x08',
3703            't' | '\t' => ret = '\t',
3704            'n' => ret = '\n',
3705            'v' => ret = '\x0b',
3706            'f' => ret = '\x0c',
3707            'r' => ret = '\x0d',
3708            'e' => ret = '\x1b',
3709            ' ' => ret = '\x20',
3710            '"' => ret = '"',
3711            '/' => ret = '/',
3712            '\\' => ret = '\\',
3713            // Unicode next line (#x85)
3714            'N' => ret = char::from_u32(0x85).unwrap(),
3715            // Unicode non-breaking space (#xA0)
3716            '_' => ret = char::from_u32(0xA0).unwrap(),
3717            // Unicode line separator (#x2028)
3718            'L' => ret = char::from_u32(0x2028).unwrap(),
3719            // Unicode paragraph separator (#x2029)
3720            'P' => ret = char::from_u32(0x2029).unwrap(),
3721            'x' => code_length = 2,
3722            'u' => code_length = 4,
3723            'U' => code_length = 8,
3724            _ => {
3725                return Err(ScanError::from_kind(
3726                    *start_mark,
3727                    ErrorKind::UnknownQuotedScalarEscape,
3728                ))
3729            }
3730        }
3731        self.skip_n_non_blank(2);
3732
3733        // Consume an arbitrary escape code.
3734        if code_length > 0 {
3735            self.input.lookahead(code_length);
3736            let mut value = 0u32;
3737            for i in 0..code_length {
3738                let c = self.input.peek_nth(i);
3739                if !is_hex(c) {
3740                    return Err(ScanError::from_kind(
3741                        *start_mark,
3742                        ErrorKind::InvalidQuotedScalarHexEscape,
3743                    ));
3744                }
3745                value = (value << 4) + as_hex(c);
3746            }
3747
3748            self.skip_n_non_blank(code_length);
3749
3750            // Handle JSON surrogate pairs: high surrogate followed by low surrogate
3751            if code_length == 4 && (0xD800..=0xDBFF).contains(&value) {
3752                self.input.lookahead(2);
3753                if self.input.peek() == '\\' && self.input.peek_nth(1) == 'u' {
3754                    self.skip_n_non_blank(2);
3755                    self.input.lookahead(4);
3756                    let mut low_value = 0u32;
3757                    for i in 0..4 {
3758                        let c = self.input.peek_nth(i);
3759                        if !is_hex(c) {
3760                            return Err(ScanError::from_kind(
3761                                *start_mark,
3762                                ErrorKind::InvalidLowSurrogateHexEscape,
3763                            ));
3764                        }
3765                        low_value = (low_value << 4) + as_hex(c);
3766                    }
3767                    if (0xDC00..=0xDFFF).contains(&low_value) {
3768                        value = 0x10000 + (((value - 0xD800) << 10) | (low_value - 0xDC00));
3769                        self.skip_n_non_blank(4);
3770                    } else {
3771                        return Err(ScanError::from_kind(
3772                            *start_mark,
3773                            ErrorKind::InvalidLowSurrogate,
3774                        ));
3775                    }
3776                } else {
3777                    return Err(ScanError::from_kind(
3778                        *start_mark,
3779                        ErrorKind::MissingLowSurrogate,
3780                    ));
3781                }
3782            } else if code_length == 4 && (0xDC00..=0xDFFF).contains(&value) {
3783                return Err(ScanError::from_kind(
3784                    *start_mark,
3785                    ErrorKind::UnpairedLowSurrogate,
3786                ));
3787            }
3788
3789            let Some(ch) = char::from_u32(value) else {
3790                return Err(ScanError::from_kind(
3791                    *start_mark,
3792                    ErrorKind::InvalidUnicodeEscape,
3793                ));
3794            };
3795            ret = ch;
3796        }
3797        Ok(ret)
3798    }
3799
3800    fn fetch_plain_scalar(&mut self) -> ScanResult {
3801        self.save_simple_key();
3802        self.disallow_simple_key();
3803
3804        let token_index = self.tokens.len();
3805        let tok = self.scan_plain_scalar()?;
3806
3807        self.insert_token(token_index, tok);
3808        Ok(())
3809    }
3810
3811    /// Scan for a plain scalar.
3812    ///
3813    /// Plain scalars are the most readable but restricted style. They may span multiple lines in
3814    /// some contexts.
3815    #[allow(clippy::too_many_lines)]
3816    fn scan_plain_scalar(&mut self) -> Result<Token<'input>, ScanError> {
3817        self.unroll_non_block_indents();
3818        let indent = self.indent + 1;
3819        let start_mark = self.mark;
3820
3821        let borrow_start = start_mark
3822            .byte_offset()
3823            .filter(|start| self.try_borrow_slice(*start, *start).is_some());
3824        let mut string = borrow_start.is_none().then(|| String::with_capacity(32));
3825        let mut has_content = false;
3826        self.buf_whitespaces.clear();
3827        self.buf_leading_break.clear();
3828        self.buf_trailing_breaks.clear();
3829        let mut end_mark = self.mark;
3830
3831        loop {
3832            self.input.lookahead(4);
3833            if (self.mark.col == 0 && self.input.next_is_document_indicator())
3834                || self.input.peek() == '#'
3835            {
3836                // BS4K: If a `#` starts a comment after some separation spaces following content
3837                // of a plain scalar in block context, and there is potential continuation on the
3838                // next line, this is invalid. We cannot decide yet if there will be continuation,
3839                // so record that a comment interrupted a plain scalar.
3840                if self.input.peek() == '#'
3841                    && has_content
3842                    && !self.buf_whitespaces.is_empty()
3843                    && self.flow_level == 0
3844                {
3845                    self.interrupted_plain_by_comment = Some(self.mark);
3846                }
3847                break;
3848            }
3849
3850            // The dash restriction applies only at the start of a plain scalar.
3851            if !has_content
3852                && self.flow_level > 0
3853                && self.input.peek() == '-'
3854                && is_flow(self.input.peek_nth(1))
3855            {
3856                return Err(self.scan_error(ErrorKind::PlainScalarStartsWithDashFlowIndicator));
3857            }
3858
3859            if !self.input.next_is_blank_or_breakz()
3860                && self.input.next_can_be_plain_scalar(self.flow_level > 0)
3861            {
3862                if self.leading_whitespace {
3863                    if has_content && string.is_none() {
3864                        let start = borrow_start.expect("borrowed scalar has a start offset");
3865                        let end = end_mark.byte_offset().ok_or_else(|| {
3866                            ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3867                        })?;
3868                        let prefix = self.try_borrow_slice(start, end).ok_or_else(|| {
3869                            ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3870                        })?;
3871                        string = Some(prefix.to_owned());
3872                    }
3873                    if self.buf_leading_break.is_empty() {
3874                        if let Some(output) = string.as_mut() {
3875                            output.push_str(&self.buf_leading_break);
3876                            output.push_str(&self.buf_trailing_breaks);
3877                        }
3878                        self.buf_trailing_breaks.clear();
3879                        self.buf_leading_break.clear();
3880                    } else {
3881                        if self.buf_trailing_breaks.is_empty() {
3882                            if let Some(output) = string.as_mut() {
3883                                output.push(' ');
3884                            }
3885                        } else {
3886                            if let Some(output) = string.as_mut() {
3887                                output.push_str(&self.buf_trailing_breaks);
3888                            }
3889                            self.buf_trailing_breaks.clear();
3890                        }
3891                        self.buf_leading_break.clear();
3892                    }
3893                    self.leading_whitespace = false;
3894                } else if !self.buf_whitespaces.is_empty() {
3895                    if let Some(output) = string.as_mut() {
3896                        output.push_str(&self.buf_whitespaces);
3897                    }
3898                    self.buf_whitespaces.clear();
3899                }
3900
3901                // We can unroll the first iteration of the loop.
3902                has_content = true;
3903                if let Some(output) = string.as_mut() {
3904                    output.push(self.input.peek());
3905                }
3906                self.skip_non_blank();
3907                if let Some(output) = string.as_mut() {
3908                    output.reserve(self.input.bufmaxlen());
3909                }
3910
3911                // Add content non-blank characters to the scalar.
3912                let mut end = false;
3913                while !end {
3914                    // Fill the buffer once and process all characters in the buffer until the next
3915                    // fetch. `next_can_be_plain_scalar` needs 2 lookahead characters, so keep one
3916                    // spare slot for normal inputs while still forcing progress for very small
3917                    // custom buffer lengths.
3918                    self.input.lookahead(self.input.bufmaxlen());
3919                    let chunk_len = self.input.bufmaxlen().saturating_sub(1).max(1);
3920                    let (stop, chars_consumed) = if let Some(output) = string.as_mut() {
3921                        self.input
3922                            .fetch_plain_scalar_chunk(output, chunk_len, self.flow_level > 0)
3923                    } else {
3924                        self.input
3925                            .skip_plain_scalar_chunk(chunk_len, self.flow_level > 0)
3926                    };
3927                    end = stop;
3928                    self.mark.offsets.chars += chars_consumed;
3929                    self.mark.col += chars_consumed;
3930                    self.mark.offsets.bytes = self.input.byte_offset();
3931                }
3932                end_mark = self.mark;
3933            }
3934
3935            // We may reach the end of a plain scalar if:
3936            //  - We reach eof
3937            //  - We reach ": "
3938            //  - We find a flow character in a flow context
3939            if !(self.input.next_is_blank() || self.input.next_is_break()) {
3940                break;
3941            }
3942
3943            // Process blank characters.
3944            self.input.lookahead(2);
3945            while self.input.next_is_blank_or_break() {
3946                if self.input.next_is_blank() {
3947                    if !self.leading_whitespace {
3948                        self.buf_whitespaces.push(self.input.peek());
3949                        self.skip_blank();
3950                    } else if (self.mark.col as isize) < indent && self.input.peek() == '\t' {
3951                        // Tabs in an indentation columns are allowed if and only if the line is
3952                        // empty. Skip to the end of the line.
3953                        self.skip_ws_to_eol(SkipTabs::Yes)?;
3954                        if !self.input.next_is_breakz() {
3955                            return Err(ScanError::from_kind(
3956                                start_mark,
3957                                ErrorKind::TabInPlainScalar,
3958                            ));
3959                        }
3960                    } else {
3961                        self.skip_blank();
3962                    }
3963                } else {
3964                    // Check if it is a first line break
3965                    if self.leading_whitespace {
3966                        self.skip_break();
3967                        self.buf_trailing_breaks.push('\n');
3968                    } else {
3969                        self.buf_whitespaces.clear();
3970                        self.skip_break();
3971                        self.buf_leading_break.push('\n');
3972                        self.leading_whitespace = true;
3973                    }
3974                }
3975                self.input.lookahead(2);
3976            }
3977
3978            // check indentation level
3979            if self.flow_level == 0 && (self.mark.col as isize) < indent {
3980                break;
3981            }
3982        }
3983
3984        if self.leading_whitespace {
3985            self.allow_simple_key();
3986        }
3987
3988        let borrowed_contents = if string.is_none() {
3989            let start = borrow_start.expect("borrowed scalar has a start offset");
3990            let end = end_mark.byte_offset().ok_or_else(|| {
3991                ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3992            })?;
3993            Some(self.try_borrow_slice(start, end).ok_or_else(|| {
3994                ScanError::from_kind(start_mark, ErrorKind::InputOffsetsWithoutSlice)
3995            })?)
3996        } else {
3997            None
3998        };
3999        let scalar_text = borrowed_contents.unwrap_or_else(|| {
4000            string
4001                .as_deref()
4002                .expect("owned plain scalar has an output buffer")
4003        });
4004        if let Some(character) = find_non_printable(scalar_text) {
4005            return Err(ScanError::from_kind(
4006                start_mark,
4007                ErrorKind::UnexpectedCharacter { character },
4008            ));
4009        }
4010        self.ensure_current_char_is_printable()?;
4011
4012        if has_content {
4013            let contents = if let Some(slice) = borrowed_contents {
4014                Cow::Borrowed(slice)
4015            } else {
4016                Cow::Owned(string.expect("owned plain scalar has an output buffer"))
4017            };
4018
4019            Ok(Token(
4020                Span::new(start_mark, end_mark),
4021                TokenType::Scalar(ScalarStyle::Plain, contents),
4022            ))
4023        } else {
4024            // `fetch_plain_scalar` must absolutely consume at least one byte. Otherwise,
4025            // `fetch_next_token` will never stop calling it. An empty plain scalar may happen with
4026            // erroneous inputs such as "{...".
4027            Err(ScanError::from_kind(
4028                start_mark,
4029                ErrorKind::UnexpectedEndOfPlainScalar,
4030            ))
4031        }
4032    }
4033
4034    fn fetch_key(&mut self) -> ScanResult {
4035        let start_mark = self.mark;
4036        if self.flow_level == 0 {
4037            // Check if we are allowed to start a new key (not necessarily simple).
4038            if !self.simple_key_allowed {
4039                return Err(self.scan_error(ErrorKind::MappingKeyNotAllowed));
4040            }
4041            self.roll_indent(
4042                start_mark.col,
4043                None,
4044                TokenType::BlockMappingStart,
4045                start_mark,
4046            )?;
4047        } else {
4048            // The scanner, upon emitting a `Key`, will prepend a `MappingStart` event.
4049            self.set_current_flow_mapping_started(true);
4050        }
4051
4052        self.remove_simple_key()?;
4053
4054        if self.flow_level == 0 {
4055            self.allow_simple_key();
4056        } else {
4057            self.disallow_simple_key();
4058        }
4059
4060        self.skip_non_blank();
4061        let end_mark = self.mark;
4062        let token_index = self.tokens.len();
4063        self.explicit_key_tab_check_pending = false;
4064        let stopped_after_comment = self.skip_yaml_whitespace()?;
4065        if self.input.peek() == '\t' {
4066            return Err(self.scan_error(ErrorKind::TabNotAllowed));
4067        }
4068        self.explicit_key_tab_check_pending = stopped_after_comment;
4069        self.insert_token(
4070            token_index,
4071            Token(Span::new(start_mark, end_mark), TokenType::Key),
4072        );
4073        Ok(())
4074    }
4075
4076    /// Fetch a value in a mapping inside of a flow collection.
4077    ///
4078    /// This must not be called if [`self.flow_level`] is 0. This ensures the rules surrounding
4079    /// values in flow collections are respected prior to calling [`fetch_value`].
4080    ///
4081    /// [`self.flow_level`]: Self::flow_level
4082    /// [`fetch_value`]: Self::fetch_value
4083    fn fetch_flow_value(&mut self) -> ScanResult {
4084        let nc = self.input.peek_nth(1);
4085
4086        // If we encounter a ':' inside a flow collection and it is not immediately
4087        // followed by a blank or breakz:
4088        //   - We must check whether an adjacent value is allowed
4089        //     `["a":[]]` is valid. If the key is double-quoted, no need for a space. This
4090        //     is needed for JSON compatibility.
4091        //   - If not, we must ensure there is a space after the ':' and before its value.
4092        //     `[a: []]` is valid while `[a:[]]` isn't. `[a:b]` is treated as `["a:b"]`.
4093        //   - But if the value is empty (null), then it's okay.
4094        // The last line is for YAMLs like `[a:]`. The ':' is followed by a ']' (which is a
4095        // flow character), but the ']' is not the value. The value is an invisible empty
4096        // space which is represented as null ('~').
4097        if self.mark.index() != self.adjacent_value_allowed_at && (nc == '[' || nc == '{') {
4098            return Err(self.scan_error(ErrorKind::FlowMappingValueAdjacentCollection));
4099        }
4100
4101        self.fetch_value()
4102    }
4103
4104    /// Fetch a value from a mapping (after a `:`).
4105    fn fetch_value(&mut self) -> ScanResult {
4106        let sk = *self.simple_keys.last().unwrap();
4107        let start_mark = self.mark;
4108        let is_implicit_flow_mapping = self.current_flow_collection_is_sequence()
4109            && !self.current_flow_mapping_started()
4110            && !self.implicit_flow_mapping_states.is_empty();
4111        if is_implicit_flow_mapping {
4112            *self.implicit_flow_mapping_states.last_mut().unwrap() =
4113                ImplicitMappingState::Inside(self.flow_level);
4114        }
4115
4116        // Skip over ':'.
4117        self.skip_non_blank();
4118        // Tabs after ':' are separation whitespace. Queue any following comment after the
4119        // Value token; indentation on subsequent lines is checked by `skip_to_next_token`.
4120        let mut trailing_tokens = VecDeque::new();
4121        let mut found_tabs = false;
4122        if matches!(self.input.look_ch(), ' ' | '\t') {
4123            let trailing_token_index = self.tokens.len();
4124            found_tabs = self.skip_ws_to_eol(SkipTabs::Yes)?.found_tabs();
4125            trailing_tokens = self.tokens.split_off(trailing_token_index);
4126        }
4127
4128        if sk.possible {
4129            // Under-indented flow entries are a compatibility extension, but implicit keys
4130            // spanning lines are rejected by both PyYAML and ruamel.yaml. Keep that extension
4131            // limited to single-line keys; explicitly marked `?` keys may span lines.
4132            let block_indent = self
4133                .indents
4134                .last()
4135                .filter(|indent| !indent.needs_block_end)
4136                .map_or(self.indent, |indent| indent.indent);
4137            if self.flow_level > 0
4138                && sk.mark.line < start_mark.line
4139                && (sk.mark.col as isize) <= block_indent
4140            {
4141                return Err(ScanError::from_kind(
4142                    start_mark,
4143                    ErrorKind::InvalidColonPlacement,
4144                ));
4145            }
4146            let token_index = self.simple_key_token_index(&sk, start_mark)?;
4147            self.validate_flow_key_indentation(&sk, token_index, start_mark)?;
4148            // insert simple key
4149            let tok = Token(Span::empty(sk.mark), TokenType::Key);
4150            self.insert_token(token_index, tok);
4151            if is_implicit_flow_mapping {
4152                if sk.mark.line < start_mark.line {
4153                    return Err(ScanError::from_kind(
4154                        start_mark,
4155                        ErrorKind::InvalidColonPlacement,
4156                    ));
4157                }
4158                self.insert_token(
4159                    token_index,
4160                    Token(Span::empty(sk.mark), TokenType::FlowMappingStart),
4161                );
4162            }
4163
4164            // Add the BLOCK-MAPPING-START token if needed.
4165            self.roll_indent(
4166                sk.mark.col,
4167                Some(sk.token_number),
4168                TokenType::BlockMappingStart,
4169                sk.mark,
4170            )?;
4171            self.roll_one_col_indent();
4172
4173            self.simple_keys.last_mut().unwrap().possible = false;
4174            self.disallow_simple_key();
4175        } else {
4176            if is_implicit_flow_mapping {
4177                self.tokens
4178                    .push_back(Token(Span::empty(start_mark), TokenType::FlowMappingStart).into());
4179            }
4180            // The ':' indicator follows a complex key.
4181            if self.flow_level == 0 {
4182                if !self.simple_key_allowed {
4183                    return Err(ScanError::from_kind(
4184                        start_mark,
4185                        ErrorKind::MappingValueNotAllowed,
4186                    ));
4187                }
4188
4189                self.roll_indent(
4190                    start_mark.col,
4191                    None,
4192                    TokenType::BlockMappingStart,
4193                    start_mark,
4194                )?;
4195            }
4196            self.roll_one_col_indent();
4197
4198            // An explicit value may start a compact block collection on this line only when
4199            // its indentation contains only spaces. Tabs still separate scalar and flow values.
4200            if self.flow_level == 0 && !found_tabs {
4201                self.allow_simple_key();
4202            } else {
4203                self.disallow_simple_key();
4204            }
4205        }
4206        self.tokens
4207            .push_back(Token(Span::empty(start_mark), TokenType::Value).into());
4208        self.tokens.append(&mut trailing_tokens);
4209
4210        Ok(())
4211    }
4212
4213    /// Keep under-indented implicit flow keys on one line, including their properties.
4214    fn validate_flow_key_indentation(
4215        &self,
4216        key: &SimpleKey,
4217        token_index: usize,
4218        colon: Marker,
4219    ) -> ScanResult {
4220        if self.flow_level == 0 || key.mark.line == colon.line {
4221            return Ok(());
4222        }
4223
4224        let block_indent = self
4225            .indents
4226            .last()
4227            .filter(|indent| !indent.needs_block_end)
4228            .map_or(self.indent, |indent| indent.indent);
4229        // The saved key mark may point to an anchor or tag before the actual content.
4230        // Check each property and the first content token; comments do not indent a key.
4231        // Stop at the content token so a collection key's children are not checked here.
4232        let mut under_indented = [key.mark.col, colon.col]
4233            .iter()
4234            .any(|&col| (col as isize) <= block_indent);
4235        for token in self.tokens.iter().skip(token_index) {
4236            if matches!(token.1, QueuedTokenType::Comment(_)) {
4237                continue;
4238            }
4239            under_indented |= (token.0.start.col as isize) <= block_indent;
4240            if !matches!(
4241                token.1,
4242                QueuedTokenType::Anchor(_) | QueuedTokenType::Tag(..)
4243            ) {
4244                break;
4245            }
4246        }
4247        if under_indented {
4248            return Err(ScanError::from_kind(
4249                colon,
4250                ErrorKind::InvalidColonPlacement,
4251            ));
4252        }
4253        Ok(())
4254    }
4255
4256    /// Add an indentation level to the stack with the given block token, if needed.
4257    ///
4258    /// An indentation level is added only if:
4259    ///   - We are not in a flow-style construct (which don't have indentation per-se).
4260    ///   - The current column is further indented than the last indent we have registered.
4261    fn roll_indent(
4262        &mut self,
4263        col: usize,
4264        number: Option<usize>,
4265        tok: TokenType<'input>,
4266        mark: Marker,
4267    ) -> ScanResult {
4268        if self.flow_level > 0 {
4269            return Ok(());
4270        }
4271
4272        // If the last indent was a non-block indent, remove it.
4273        // This means that we prepared an indent that we thought we wouldn't use, but realized just
4274        // now that it is a block indent.
4275        if self.indent <= col as isize {
4276            if let Some(indent) = self.indents.last() {
4277                if !indent.needs_block_end {
4278                    self.indent = indent.indent;
4279                    self.indents.pop();
4280                }
4281            }
4282        }
4283
4284        if self.indent < col as isize {
4285            if self.block_level >= self.options.block_nesting_limit {
4286                return Err(ScanError::from_kind(
4287                    mark,
4288                    ErrorKind::RecursionLimitExceeded,
4289                ));
4290            }
4291            self.indents.push(Indent {
4292                indent: self.indent,
4293                needs_block_end: true,
4294            });
4295            self.block_level += 1;
4296            self.indent = col as isize;
4297            let tokens_parsed = self.tokens_parsed;
4298            match number {
4299                Some(n) => self.insert_token(n - tokens_parsed, Token(Span::empty(mark), tok)),
4300                None => self.tokens.push_back(Token(Span::empty(mark), tok).into()),
4301            }
4302        }
4303        Ok(())
4304    }
4305
4306    /// Pop indentation levels from the stack as much as needed.
4307    ///
4308    /// Indentation levels are popped from the stack while they are further indented than `col`.
4309    /// If we are in a flow-style construct (which don't have indentation per-se), this function
4310    /// does nothing.
4311    fn unroll_indent(&mut self, col: isize) {
4312        if self.flow_level > 0 {
4313            return;
4314        }
4315        while self.indent > col {
4316            let indent = self.indents.pop().unwrap();
4317            self.indent = indent.indent;
4318            if indent.needs_block_end {
4319                debug_assert!(self.block_level > 0);
4320                self.block_level -= 1;
4321                self.tokens
4322                    .push_back(Token(Span::empty(self.mark), TokenType::BlockEnd).into());
4323            }
4324        }
4325    }
4326
4327    /// Add an indentation level of 1 column that does not start a block.
4328    ///
4329    /// See the documentation of [`Indent::needs_block_end`] for more details.
4330    /// An indentation is not added if we are inside a flow level or if the last indent is already
4331    /// a non-block indent.
4332    fn roll_one_col_indent(&mut self) {
4333        if self.flow_level == 0 && self.indents.last().is_some_and(|x| x.needs_block_end) {
4334            self.indents.push(Indent {
4335                indent: self.indent,
4336                needs_block_end: false,
4337            });
4338            self.indent += 1;
4339        }
4340    }
4341
4342    /// Unroll all last indents created with [`Self::roll_one_col_indent`].
4343    fn unroll_non_block_indents(&mut self) {
4344        while let Some(indent) = self.indents.last() {
4345            if indent.needs_block_end {
4346                break;
4347            }
4348            self.indent = indent.indent;
4349            self.indents.pop();
4350        }
4351    }
4352
4353    /// Mark the next token to be inserted as a potential simple key.
4354    fn save_simple_key(&mut self) {
4355        if self.simple_key_allowed {
4356            let required = self.flow_level == 0
4357                && self.indent == (self.mark.col as isize)
4358                && self.indents.last().unwrap().needs_block_end;
4359
4360            if let Some(last) = self.simple_keys.last_mut() {
4361                *last = SimpleKey {
4362                    mark: self.mark,
4363                    possible: true,
4364                    required,
4365                    token_number: self.tokens_parsed + self.tokens.len(),
4366                };
4367            }
4368        }
4369    }
4370
4371    fn remove_simple_key(&mut self) -> ScanResult {
4372        let last = self.simple_keys.last_mut().unwrap();
4373        if last.possible && last.required {
4374            return Err(Self::simple_key_expected(last.mark));
4375        }
4376
4377        last.possible = false;
4378        Ok(())
4379    }
4380
4381    /// Return whether the scanner is inside a block but outside of a flow sequence.
4382    fn is_within_block(&self) -> bool {
4383        !self.indents.is_empty()
4384    }
4385
4386    /// If an implicit mapping had started, end it.
4387    ///
4388    /// This function does not pop the state in [`implicit_flow_mapping_states`].
4389    ///
4390    /// [`implicit_flow_mapping_states`]: Self::implicit_flow_mapping_states
4391    fn end_implicit_mapping(&mut self, mark: Marker, flow_level: usize) {
4392        if self
4393            .implicit_flow_mapping_states
4394            .last()
4395            .is_some_and(|state| *state == ImplicitMappingState::Inside(flow_level))
4396        {
4397            *self.implicit_flow_mapping_states.last_mut().unwrap() = ImplicitMappingState::Possible;
4398            self.set_current_flow_mapping_started(false);
4399            self.tokens
4400                .push_back(Token(Span::empty(mark), TokenType::FlowMappingEnd).into());
4401        }
4402    }
4403
4404    fn current_flow_collection_is_sequence(&self) -> bool {
4405        self.flow_markers
4406            .last()
4407            .is_some_and(|(_, bracket)| *bracket == '[')
4408    }
4409
4410    fn current_flow_mapping_started(&self) -> bool {
4411        self.flow_mapping_started.last().copied().unwrap_or(false)
4412    }
4413
4414    fn set_current_flow_mapping_started(&mut self, started: bool) {
4415        if let Some(current) = self.flow_mapping_started.last_mut() {
4416            *current = started;
4417        }
4418    }
4419}
4420
4421/// Chomping, how final line breaks and trailing empty lines are interpreted.
4422///
4423/// See YAML spec 8.1.1.2.
4424#[derive(PartialEq, Eq)]
4425enum Chomping {
4426    /// The final line break and any trailing empty lines are excluded.
4427    Strip,
4428    /// The final line break is preserved, but trailing empty lines are excluded.
4429    Clip,
4430    /// The final line break and trailing empty lines are included.
4431    Keep,
4432}
4433
4434#[cfg(test)]
4435mod test {
4436    use alloc::{
4437        borrow::{Cow, ToOwned},
4438        rc::Rc,
4439        string::String,
4440        vec,
4441        vec::Vec,
4442    };
4443    use core::cell::Cell;
4444
4445    use crate::error::{ErrorKind, ScanError};
4446    use crate::{
4447        input::{str::StrInput, BorrowedInput, BufferedInput, Input},
4448        scanner::{
4449            Comment, Marker, Placement, QueuedToken, QueuedTokenType, ScalarStyle, Scanner, Span,
4450            Token, TokenType,
4451        },
4452    };
4453
4454    struct CountingChars {
4455        chars: alloc::vec::IntoIter<char>,
4456        read: Rc<Cell<usize>>,
4457    }
4458
4459    impl Iterator for CountingChars {
4460        type Item = char;
4461
4462        fn next(&mut self) -> Option<Self::Item> {
4463            let next = self.chars.next();
4464            if next.is_some() {
4465                self.read.set(self.read.get() + 1);
4466            }
4467            next
4468        }
4469    }
4470
4471    struct SlicingOnlyInput<'input> {
4472        inner: StrInput<'input>,
4473        expose_slice: bool,
4474    }
4475
4476    impl<'input> SlicingOnlyInput<'input> {
4477        fn new(source: &'input str, expose_slice: bool) -> Self {
4478            Self {
4479                inner: StrInput::new(source),
4480                expose_slice,
4481            }
4482        }
4483    }
4484
4485    impl Input for SlicingOnlyInput<'_> {
4486        fn lookahead(&mut self, count: usize) {
4487            self.inner.lookahead(count);
4488        }
4489
4490        fn buflen(&self) -> usize {
4491            self.inner.buflen()
4492        }
4493
4494        fn bufmaxlen(&self) -> usize {
4495            self.inner.bufmaxlen()
4496        }
4497
4498        fn raw_read_ch(&mut self) -> char {
4499            self.inner.raw_read_ch()
4500        }
4501
4502        fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
4503            self.inner.raw_read_non_breakz_ch()
4504        }
4505
4506        fn skip(&mut self) {
4507            self.inner.skip();
4508        }
4509
4510        fn skip_n(&mut self, count: usize) {
4511            self.inner.skip_n(count);
4512        }
4513
4514        fn peek(&self) -> char {
4515            self.inner.peek()
4516        }
4517
4518        fn peek_nth(&self, n: usize) -> char {
4519            self.inner.peek_nth(n)
4520        }
4521
4522        fn byte_offset(&self) -> Option<usize> {
4523            self.inner.byte_offset()
4524        }
4525
4526        fn slice_bytes(&self, start: usize, end: usize) -> Option<&str> {
4527            if self.expose_slice {
4528                self.inner.slice_bytes(start, end)
4529            } else {
4530                None
4531            }
4532        }
4533    }
4534
4535    impl<'input> BorrowedInput<'input> for SlicingOnlyInput<'input> {
4536        fn slice_borrowed(&self, _start: usize, _end: usize) -> Option<&'input str> {
4537            None
4538        }
4539    }
4540
4541    struct SmallReportedBufferInput<'input> {
4542        inner: StrInput<'input>,
4543        reported_bufmaxlen: usize,
4544    }
4545
4546    impl<'input> SmallReportedBufferInput<'input> {
4547        fn new(source: &'input str, reported_bufmaxlen: usize) -> Self {
4548            Self {
4549                inner: StrInput::new(source),
4550                reported_bufmaxlen,
4551            }
4552        }
4553    }
4554
4555    impl Input for SmallReportedBufferInput<'_> {
4556        fn lookahead(&mut self, count: usize) {
4557            self.inner.lookahead(count);
4558        }
4559
4560        fn buflen(&self) -> usize {
4561            self.inner.buflen()
4562        }
4563
4564        fn bufmaxlen(&self) -> usize {
4565            self.reported_bufmaxlen
4566        }
4567
4568        fn raw_read_ch(&mut self) -> char {
4569            self.inner.raw_read_ch()
4570        }
4571
4572        fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
4573            self.inner.raw_read_non_breakz_ch()
4574        }
4575
4576        fn skip(&mut self) {
4577            self.inner.skip();
4578        }
4579
4580        fn skip_n(&mut self, count: usize) {
4581            self.inner.skip_n(count);
4582        }
4583
4584        fn peek(&self) -> char {
4585            self.inner.peek()
4586        }
4587
4588        fn peek_nth(&self, n: usize) -> char {
4589            self.inner.peek_nth(n)
4590        }
4591    }
4592
4593    impl<'input> BorrowedInput<'input> for SmallReportedBufferInput<'input> {
4594        fn slice_borrowed(&self, start: usize, end: usize) -> Option<&'input str> {
4595            self.inner.slice_borrowed(start, end)
4596        }
4597    }
4598
4599    #[test]
4600    fn anchor_character_set_allows_colon_and_rejects_flow_indicators() {
4601        use super::is_anchor_char;
4602
4603        assert!(is_anchor_char('x'));
4604        assert!(is_anchor_char('-'));
4605        assert!(is_anchor_char('_'));
4606        assert!(is_anchor_char(':'));
4607        assert!(is_anchor_char('#'));
4608        assert!(is_anchor_char('/'));
4609        assert!(is_anchor_char('?'));
4610
4611        for c in [',', '[', ']', '{', '}', ' ', '\t', '\n', '\r', '\0'] {
4612            assert!(
4613                !is_anchor_char(c),
4614                "character {c:?} must not be accepted in anchor/alias names"
4615            );
4616        }
4617    }
4618
4619    #[test]
4620    fn flow_simple_key_length_limit_bounds_buffering() {
4621        let mut yaml = String::from("[\n\"start\"\n");
4622        for _ in 0..600 {
4623            yaml.push_str("\"x\"\n");
4624        }
4625        let total_chars = yaml.chars().count();
4626        let read = Rc::new(Cell::new(0));
4627        let chars = yaml.chars().collect::<Vec<_>>().into_iter();
4628        let mut scanner = Scanner::new(BufferedInput::new(CountingChars {
4629            chars,
4630            read: Rc::clone(&read),
4631        }));
4632
4633        assert!(matches!(
4634            scanner.next_token().unwrap().unwrap().1,
4635            TokenType::StreamStart
4636        ));
4637
4638        let token = scanner.next_token().unwrap().unwrap();
4639        assert!(matches!(token.1, TokenType::FlowSequenceStart));
4640
4641        let token = scanner.next_token().unwrap().unwrap();
4642        assert!(matches!(
4643            token.1,
4644            TokenType::Scalar(_, ref value) if value == "start"
4645        ));
4646        assert!(
4647            read.get() < total_chars,
4648            "scanner consumed all {total_chars} chars before yielding the first flow scalar"
4649        );
4650        assert!(
4651            read.get() <= scanner.options.simple_key_max_lookahead + 128,
4652            "scanner read {} chars before yielding the first flow scalar",
4653            read.get()
4654        );
4655    }
4656
4657    #[test]
4658    fn block_scalar_indent_tolerates_small_reported_bufmaxlen() {
4659        let mut scanner = Scanner::new(SmallReportedBufferInput::new("|\n  value\n", 0));
4660
4661        let scalar = scanner
4662            .find_map(
4663                |token| match token.expect("valid YAML should scan without errors") {
4664                    Token(_, TokenType::Scalar(ScalarStyle::Literal, value)) => {
4665                        Some(value.into_owned())
4666                    }
4667                    _ => None,
4668                },
4669            )
4670            .expect("expected block scalar token");
4671
4672        assert_eq!(scalar, "value\n");
4673    }
4674
4675    #[test]
4676    fn plain_scalar_chunk_tolerates_small_reported_bufmaxlen() {
4677        let mut scanner = Scanner::new(SmallReportedBufferInput::new("plain\n", 0));
4678
4679        let scalar = scanner
4680            .find_map(
4681                |token| match token.expect("valid YAML should scan without errors") {
4682                    Token(_, TokenType::Scalar(ScalarStyle::Plain, value)) => {
4683                        Some(value.into_owned())
4684                    }
4685                    _ => None,
4686                },
4687            )
4688            .expect("expected plain scalar token");
4689
4690        assert_eq!(scalar, "plain");
4691    }
4692
4693    fn first_token_slice(
4694        yaml: &str,
4695        matches_token: impl Fn(&TokenType<'_>) -> bool,
4696    ) -> Option<String> {
4697        let mut scanner = Scanner::new(StrInput::new(yaml));
4698
4699        loop {
4700            let token = scanner
4701                .next_token()
4702                .expect("scanner should accept the test YAML")?;
4703            if matches_token(&token.1) {
4704                return token.0.slice(yaml).map(ToOwned::to_owned);
4705            }
4706        }
4707    }
4708
4709    #[test]
4710    fn flow_indicator_token_spans_cover_only_the_indicator() {
4711        assert_eq!(
4712            first_token_slice("[ # c\n  a]\n", |token| matches!(
4713                token,
4714                TokenType::FlowSequenceStart
4715            ))
4716            .as_deref(),
4717            Some("[")
4718        );
4719        assert_eq!(
4720            first_token_slice("{ # c\n  a: b}\n", |token| matches!(
4721                token,
4722                TokenType::FlowMappingStart
4723            ))
4724            .as_deref(),
4725            Some("{")
4726        );
4727        assert_eq!(
4728            first_token_slice("[a] # c\n", |token| matches!(
4729                token,
4730                TokenType::FlowSequenceEnd
4731            ))
4732            .as_deref(),
4733            Some("]")
4734        );
4735        assert_eq!(
4736            first_token_slice("{a: b} # c\n", |token| matches!(
4737                token,
4738                TokenType::FlowMappingEnd
4739            ))
4740            .as_deref(),
4741            Some("}")
4742        );
4743        assert_eq!(
4744            first_token_slice("[a, # c\nb]\n", |token| matches!(
4745                token,
4746                TokenType::FlowEntry
4747            ))
4748            .as_deref(),
4749            Some(",")
4750        );
4751    }
4752
4753    #[test]
4754    fn explicit_key_token_span_covers_only_the_indicator() {
4755        assert_eq!(
4756            first_token_slice("? # c\n: value\n", |token| matches!(token, TokenType::Key))
4757                .as_deref(),
4758            Some("?")
4759        );
4760    }
4761
4762    #[test]
4763    fn comment_capture_does_not_change_leading_whitespace() {
4764        let mut scanner = Scanner::new(StrInput::new("# comment\n"));
4765
4766        let token = scanner.scan_comment_token().unwrap();
4767
4768        assert!(scanner.leading_whitespace);
4769        assert!(matches!(token.1, TokenType::Comment(ref comment) if comment.text == " comment"));
4770
4771        let mut scanner = Scanner::new(BufferedInput::new("# streaming\n".chars()));
4772        scanner.input.lookahead(1);
4773
4774        let token = scanner.scan_comment_token().unwrap();
4775
4776        assert!(scanner.leading_whitespace);
4777        assert!(matches!(token.1, TokenType::Comment(ref comment) if comment.text == " streaming"));
4778    }
4779
4780    #[test]
4781    fn comment_capture_falls_back_to_owned_slice_when_borrow_unavailable() {
4782        let mut scanner = Scanner::new(SlicingOnlyInput::new("# sliced\n", true));
4783        scanner.input.lookahead(2);
4784        assert_eq!(scanner.input.peek_nth(1), ' ');
4785
4786        let token = scanner.scan_comment_token().unwrap();
4787
4788        assert!(matches!(token.1, TokenType::Comment(ref comment)
4789            if matches!(comment.text, Cow::Owned(ref text) if text == " sliced")));
4790    }
4791
4792    #[test]
4793    fn comment_capture_errors_when_offsets_have_no_slice() {
4794        let mut scanner = Scanner::new(SlicingOnlyInput::new("# broken\n", false));
4795
4796        let error = scanner.scan_comment_token().unwrap_err();
4797
4798        assert_eq!(error.kind(), &ErrorKind::InputOffsetsWithoutSlice);
4799    }
4800
4801    #[test]
4802    fn disabled_comments_do_not_require_input_slicing() {
4803        let options = crate::options! { emit_comments: false };
4804        let scanner = Scanner::with_options(
4805            SlicingOnlyInput::new("# ignored\nkey: value\n", false),
4806            options,
4807        );
4808
4809        let tokens = scanner
4810            .collect::<Result<Vec<_>, _>>()
4811            .expect("ignored comments should not capture their payload");
4812
4813        assert!(tokens
4814            .iter()
4815            .all(|token| !matches!(token.1, TokenType::Comment(_))));
4816    }
4817
4818    #[test]
4819    fn queued_token_roundtrips_public_token_variants() {
4820        let span = Span::new(Marker::new(0, 1, 0), Marker::new(7, 1, 7));
4821        let tokens = [
4822            Token(span, TokenType::StreamStart),
4823            Token(span, TokenType::StreamEnd),
4824            Token(span, TokenType::VersionDirective(1, 2)),
4825            Token(
4826                span,
4827                TokenType::TagDirective(Cow::Borrowed("!app!"), Cow::Borrowed("tag:app.example,")),
4828            ),
4829            Token(span, TokenType::DocumentStart),
4830            Token(span, TokenType::DocumentEnd),
4831            Token(span, TokenType::BlockSequenceStart),
4832            Token(span, TokenType::BlockMappingStart),
4833            Token(span, TokenType::BlockEnd),
4834            Token(span, TokenType::FlowSequenceStart),
4835            Token(span, TokenType::FlowSequenceEnd),
4836            Token(span, TokenType::FlowMappingStart),
4837            Token(span, TokenType::FlowMappingEnd),
4838            Token(span, TokenType::BlockEntry),
4839            Token(span, TokenType::FlowEntry),
4840            Token(span, TokenType::Key),
4841            Token(span, TokenType::Value),
4842            Token(span, TokenType::Alias(Cow::Borrowed("alias"))),
4843            Token(span, TokenType::Anchor(Cow::Borrowed("anchor"))),
4844            Token(
4845                span,
4846                TokenType::Tag(Cow::Borrowed("!"), Cow::Borrowed("tag")),
4847            ),
4848            Token(
4849                span,
4850                TokenType::Scalar(ScalarStyle::Literal, Cow::Borrowed("scalar")),
4851            ),
4852            Token(
4853                span,
4854                TokenType::Comment(
4855                    Comment::new(Cow::Borrowed(" comment")).with_placement(Placement::Right),
4856                ),
4857            ),
4858            Token(
4859                span,
4860                TokenType::ReservedDirective(
4861                    "reserved".to_owned(),
4862                    vec!["one".to_owned(), "two".to_owned()],
4863                ),
4864            ),
4865        ];
4866
4867        for token in tokens {
4868            let queued: QueuedToken = token.clone().into();
4869
4870            assert_eq!(queued.into_public(), token);
4871        }
4872    }
4873
4874    #[test]
4875    fn comment_skipping_path_consumes_comment_without_tokenizing_it() {
4876        let mut scanner = Scanner::new(StrInput::new("# skipped\nnext: value\n"));
4877
4878        scanner.skip_yaml_whitespace().unwrap();
4879
4880        assert!(scanner.tokens.is_empty());
4881        assert_eq!(scanner.mark.line(), 2);
4882        assert_eq!(scanner.mark.col(), 0);
4883    }
4884
4885    #[test]
4886    fn yaml_whitespace_can_stop_after_queued_comment() {
4887        let mut scanner = Scanner::new(StrInput::new(" # queued\n# later\n"));
4888
4889        assert!(scanner.skip_yaml_whitespace().unwrap());
4890
4891        assert_eq!(scanner.tokens.len(), 1);
4892        assert!(matches!(
4893            scanner.tokens.front().unwrap().1,
4894            QueuedTokenType::Comment(ref comment) if comment.text == " queued"
4895        ));
4896        assert_eq!(scanner.mark.line(), 1);
4897        assert_eq!(scanner.mark.col(), 9);
4898    }
4899
4900    #[test]
4901    fn token_skip_can_stop_after_queued_comment() {
4902        let mut scanner = Scanner::new(StrInput::new("# first\n# second\n"));
4903
4904        assert!(scanner.skip_to_next_token().unwrap().queued_comment);
4905
4906        assert_eq!(scanner.tokens.len(), 1);
4907        assert!(matches!(
4908            scanner.tokens.front().unwrap().1,
4909            QueuedTokenType::Comment(ref comment) if comment.text == " first"
4910        ));
4911        assert_eq!(scanner.mark.line(), 2);
4912        assert_eq!(scanner.mark.col(), 0);
4913    }
4914
4915    #[test]
4916    fn token_skip_stops_after_one_ignored_comment_without_queuing_it() {
4917        let options = crate::options! { emit_comments: false };
4918        let mut scanner =
4919            Scanner::with_options(StrInput::new("# first\n# second\nvalue\n"), options);
4920
4921        let outcome = scanner.skip_to_next_token().unwrap();
4922
4923        assert!(outcome.saw_comment);
4924        assert!(!outcome.queued_comment);
4925        assert!(scanner.tokens.is_empty());
4926        assert_eq!((scanner.mark.line(), scanner.mark.col()), (2, 0));
4927        assert_eq!(scanner.input.look_ch(), '#');
4928    }
4929
4930    #[test]
4931    fn yaml_whitespace_stops_after_one_separated_ignored_comment() {
4932        let options = crate::options! { emit_comments: false };
4933        let mut scanner =
4934            Scanner::with_options(StrInput::new(" # first\n# second\nvalue\n"), options);
4935
4936        assert!(scanner.skip_yaml_whitespace().unwrap());
4937
4938        assert!(scanner.tokens.is_empty());
4939        assert_eq!((scanner.mark.line(), scanner.mark.col()), (1, 8));
4940        assert_eq!(scanner.input.look_ch(), '\n');
4941    }
4942
4943    #[test]
4944    fn scanner_skips_consecutive_leading_ignored_comments_before_next_token() {
4945        let options = crate::options! { emit_comments: false };
4946        let mut scanner =
4947            Scanner::with_options(StrInput::new("# first\n# second\nvalue\n"), options);
4948
4949        assert!(matches!(
4950            scanner.next_token().unwrap().unwrap().1,
4951            TokenType::StreamStart
4952        ));
4953        assert!(matches!(
4954            scanner.next_token().unwrap().unwrap().1,
4955            TokenType::Scalar(ScalarStyle::Plain, ref value) if value == "value"
4956        ));
4957        assert!(scanner
4958            .tokens
4959            .iter()
4960            .all(|QueuedToken(_, token)| !matches!(token, QueuedTokenType::Comment(_))));
4961    }
4962
4963    #[test]
4964    fn scanner_emits_first_leading_comment_before_scanning_next_comment() {
4965        let mut scanner = Scanner::new(StrInput::new("# first\n# second\nkey: value\n"));
4966
4967        assert!(matches!(
4968            scanner.next_token().unwrap().unwrap().1,
4969            TokenType::StreamStart
4970        ));
4971        assert!(matches!(
4972            scanner.next_token().unwrap().unwrap().1,
4973            TokenType::Comment(ref comment) if comment.text == " first"
4974        ));
4975        assert!(scanner.tokens.is_empty());
4976        assert!(matches!(
4977            scanner.next_token().unwrap().unwrap().1,
4978            TokenType::Comment(ref comment) if comment.text == " second"
4979        ));
4980    }
4981
4982    #[test]
4983    fn scanner_emits_quoted_scalar_comment_before_scanning_following_value() {
4984        let mut scanner = Scanner::new(StrInput::new("\"key\" # quoted\n: value\n"));
4985
4986        assert!(matches!(
4987            scanner.next_token().unwrap().unwrap().1,
4988            TokenType::StreamStart
4989        ));
4990        assert!(matches!(
4991            scanner.next_token().unwrap().unwrap().1,
4992            TokenType::Scalar(ScalarStyle::DoubleQuoted, ref value) if value == "key"
4993        ));
4994        assert!(matches!(
4995            scanner.next_token().unwrap().unwrap().1,
4996            TokenType::Comment(ref comment) if comment.text == " quoted"
4997        ));
4998    }
4999
5000    #[test]
5001    fn flow_scalar_comment_disables_adjacent_value_lookahead() {
5002        let mut scanner = Scanner::new(StrInput::new("\"key\"\n# quoted\n: value\n"));
5003
5004        scanner.fetch_flow_scalar(false).unwrap();
5005
5006        assert_eq!(scanner.adjacent_value_allowed_at, usize::MAX);
5007        assert!(matches!(
5008            scanner.tokens.front().unwrap().1,
5009            QueuedTokenType::Scalar(ScalarStyle::DoubleQuoted, ref value) if value == "key"
5010        ));
5011        assert!(scanner.tokens.iter().any(|QueuedToken(_, token)| matches!(
5012            token,
5013            QueuedTokenType::Comment(comment) if comment.text == " quoted"
5014        )));
5015    }
5016
5017    #[test]
5018    fn ignored_flow_scalar_comment_still_disables_adjacent_value_lookahead() {
5019        let options = crate::options! { emit_comments: false };
5020        let mut scanner =
5021            Scanner::with_options(StrInput::new("\"key\"\n# ignored\n: value\n"), options);
5022
5023        scanner.fetch_flow_scalar(false).unwrap();
5024
5025        assert_eq!(scanner.adjacent_value_allowed_at, usize::MAX);
5026        assert!(scanner
5027            .tokens
5028            .iter()
5029            .all(|QueuedToken(_, token)| { !matches!(token, QueuedTokenType::Comment(_)) }));
5030    }
5031
5032    #[test]
5033    fn deferred_error_waits_for_all_comment_tokens() {
5034        let mut scanner = Scanner::new(StrInput::new("# first\n# second\n@\n"));
5035
5036        assert!(matches!(
5037            scanner.next_token().unwrap().unwrap().1,
5038            TokenType::StreamStart
5039        ));
5040        assert!(matches!(
5041            scanner.next_token().unwrap().unwrap().1,
5042            TokenType::Comment(ref comment) if comment.text == " first"
5043        ));
5044        assert!(matches!(
5045            scanner.next_token().unwrap().unwrap().1,
5046            TokenType::Comment(ref comment) if comment.text == " second"
5047        ));
5048
5049        let error = scanner.next_token().unwrap_err();
5050
5051        assert_eq!(
5052            error.kind(),
5053            &ErrorKind::UnexpectedCharacter { character: '@' }
5054        );
5055    }
5056
5057    /// Ensure anchors scanned from `StrInput` are returned as `Cow::Borrowed`.
5058    #[test]
5059    fn anchor_name_is_borrowed_for_str_input() {
5060        let mut scanner = Scanner::new(StrInput::new("&anch\n"));
5061
5062        loop {
5063            let tok = scanner
5064                .next_token()
5065                .expect("valid YAML must scan without errors")
5066                .expect("scanner must eventually produce a token");
5067            if let TokenType::Anchor(name) = tok.1 {
5068                assert!(matches!(name, Cow::Borrowed("anch")));
5069                break;
5070            }
5071        }
5072    }
5073
5074    #[test]
5075    fn anchor_name_rejects_non_printable_control_chars() {
5076        let mut scanner = Scanner::new(StrInput::new("&foo\u{0001}\n"));
5077
5078        scanner.next_token().unwrap();
5079        assert_eq!(
5080            scanner.next_token().unwrap_err().kind(),
5081            &ErrorKind::UnexpectedCharacter {
5082                character: '\u{0001}'
5083            }
5084        );
5085    }
5086
5087    #[test]
5088    fn alias_name_rejects_non_printable_control_chars() {
5089        let mut scanner = Scanner::new(StrInput::new("*foo\u{0001}\n"));
5090
5091        scanner.next_token().unwrap();
5092        assert_eq!(
5093            scanner.next_token().unwrap_err().kind(),
5094            &ErrorKind::UnexpectedCharacter {
5095                character: '\u{0001}'
5096            }
5097        );
5098    }
5099
5100    #[test]
5101    fn alias_name_is_borrowed_for_str_input() {
5102        let mut scanner = Scanner::new(StrInput::new("*anch\n"));
5103
5104        loop {
5105            let tok = scanner
5106                .next_token()
5107                .expect("valid YAML must scan without errors")
5108                .expect("scanner must eventually produce a token");
5109            if let TokenType::Alias(name) = tok.1 {
5110                assert!(matches!(name, Cow::Borrowed("anch")));
5111                break;
5112            }
5113        }
5114    }
5115
5116    #[test]
5117    fn alias_name_scans_colon_as_part_of_name() {
5118        let mut scanner = Scanner::new(StrInput::new("*foo: bar\n"));
5119
5120        loop {
5121            let tok = scanner
5122                .next_token()
5123                .expect("scanner must not fail before alias token")
5124                .expect("scanner must eventually emit an alias token");
5125
5126            if let TokenType::Alias(name) = tok.1 {
5127                assert_eq!(name.as_ref(), "foo:");
5128                break;
5129            }
5130        }
5131    }
5132
5133    #[test]
5134    fn anchor_name_scans_colon_as_part_of_name() {
5135        let mut scanner = Scanner::new(StrInput::new("&foo: bar\n"));
5136
5137        loop {
5138            let tok = scanner
5139                .next_token()
5140                .expect("scanner must not fail before anchor token")
5141                .expect("scanner must eventually emit an anchor token");
5142
5143            if let TokenType::Anchor(name) = tok.1 {
5144                assert_eq!(name.as_ref(), "foo:");
5145                break;
5146            }
5147        }
5148    }
5149
5150    /// Ensure `%TAG` directive handle and prefix are borrowed when they are verbatim (no escapes).
5151    #[test]
5152    fn tag_directive_parts_are_borrowed_for_str_input() {
5153        let mut scanner = Scanner::new(StrInput::new("%TAG !e! tag:example.com,2000:app/\n"));
5154
5155        loop {
5156            let tok = scanner
5157                .next_token()
5158                .expect("valid YAML must scan without errors")
5159                .expect("scanner must eventually produce a token");
5160            if let TokenType::TagDirective(handle, prefix) = tok.1 {
5161                assert!(matches!(handle, Cow::Borrowed("!e!")));
5162                assert!(matches!(prefix, Cow::Borrowed("tag:example.com,2000:app/")));
5163                break;
5164            }
5165        }
5166    }
5167
5168    #[test]
5169    fn tag_directive_parts_are_owned_for_buffered_input() {
5170        let mut scanner = Scanner::new(BufferedInput::new(
5171            "%TAG !e! tag:example.com,2000:app/\n".chars(),
5172        ));
5173
5174        loop {
5175            let tok = scanner
5176                .next_token()
5177                .expect("valid YAML must scan without errors")
5178                .expect("scanner must eventually produce a token");
5179            if let TokenType::TagDirective(handle, prefix) = tok.1 {
5180                assert!(matches!(handle, Cow::Owned(_)));
5181                assert_eq!(&*handle, "!e!");
5182                assert!(matches!(prefix, Cow::Owned(_)));
5183                assert_eq!(&*prefix, "tag:example.com,2000:app/");
5184                break;
5185            }
5186        }
5187    }
5188
5189    #[test]
5190    fn buffered_tag_directive_decodes_prefix_escape() {
5191        let mut scanner = Scanner::new(BufferedInput::new(
5192            "%TAG !e! %74ag:example.com,2000:app/\n".chars(),
5193        ));
5194
5195        loop {
5196            let tok = scanner
5197                .next_token()
5198                .expect("valid YAML must scan without errors")
5199                .expect("scanner must eventually produce a token");
5200            if let TokenType::TagDirective(handle, prefix) = tok.1 {
5201                assert_eq!(&*handle, "!e!");
5202                assert!(matches!(prefix, Cow::Owned(_)));
5203                assert_eq!(&*prefix, "tag:example.com,2000:app/");
5204                break;
5205            }
5206        }
5207    }
5208
5209    #[test]
5210    fn local_tag_combines_handle_text_with_escaped_suffix() {
5211        let mut scanner = Scanner::new(StrInput::new("!foo%20bar value\n"));
5212
5213        loop {
5214            let tok = scanner
5215                .next_token()
5216                .expect("valid YAML must scan without errors")
5217                .expect("scanner must eventually produce a token");
5218            if let TokenType::Tag(handle, suffix) = tok.1 {
5219                assert!(matches!(handle, Cow::Borrowed("!")));
5220                assert!(matches!(suffix, Cow::Owned(_)));
5221                assert_eq!(&*suffix, "foo bar");
5222                break;
5223            }
5224        }
5225    }
5226
5227    #[test]
5228    fn secondary_tag_requires_suffix_in_borrowed_and_buffered_paths() {
5229        assert_eq!(
5230            first_scanner_error_kind("!! value\n"),
5231            ErrorKind::MissingTagUri
5232        );
5233        assert_eq!(
5234            first_buffered_scanner_error_kind("!! value\n"),
5235            ErrorKind::MissingTagUri
5236        );
5237    }
5238
5239    #[test]
5240    fn plain_scalar_is_borrowed_when_whitespace_free_for_str_input() {
5241        let mut scanner = Scanner::new(StrInput::new("foo\n"));
5242
5243        loop {
5244            let tok = scanner
5245                .next_token()
5246                .expect("valid YAML must scan without errors")
5247                .expect("scanner must eventually produce a token");
5248            if let TokenType::Scalar(_, value) = tok.1 {
5249                assert!(matches!(value, Cow::Borrowed("foo")));
5250                break;
5251            }
5252        }
5253    }
5254
5255    #[test]
5256    fn plain_scalar_is_borrowed_when_whitespace_present_for_str_input() {
5257        let mut scanner = Scanner::new(StrInput::new("foo bar\n"));
5258
5259        loop {
5260            let tok = scanner
5261                .next_token()
5262                .expect("valid YAML must scan without errors")
5263                .expect("scanner must eventually produce a token");
5264            if let TokenType::Scalar(_, value) = tok.1 {
5265                assert!(matches!(value, Cow::Borrowed("foo bar")));
5266                break;
5267            }
5268        }
5269    }
5270
5271    #[test]
5272    fn single_quoted_scalar_is_borrowed_when_verbatim_for_str_input() {
5273        let mut scanner = Scanner::new(StrInput::new("'foo bar'\n"));
5274
5275        loop {
5276            let tok = scanner
5277                .next_token()
5278                .expect("valid YAML must scan without errors")
5279                .expect("scanner must eventually produce a token");
5280            if let TokenType::Scalar(_, value) = tok.1 {
5281                assert!(matches!(value, Cow::Borrowed("foo bar")));
5282                break;
5283            }
5284        }
5285    }
5286
5287    #[test]
5288    fn single_quoted_scalar_is_owned_when_quote_is_escaped_for_str_input() {
5289        let mut scanner = Scanner::new(StrInput::new("'foo''bar'\n"));
5290
5291        loop {
5292            let tok = scanner
5293                .next_token()
5294                .expect("valid YAML must scan without errors")
5295                .expect("scanner must eventually produce a token");
5296            if let TokenType::Scalar(_, value) = tok.1 {
5297                assert!(matches!(value, Cow::Owned(_)));
5298                assert_eq!(&*value, "foo'bar");
5299                break;
5300            }
5301        }
5302    }
5303
5304    #[test]
5305    fn double_quoted_scalar_is_borrowed_when_verbatim_for_str_input() {
5306        let mut scanner = Scanner::new(StrInput::new("\"foo bar\"\n"));
5307
5308        loop {
5309            let tok = scanner
5310                .next_token()
5311                .expect("valid YAML must scan without errors")
5312                .expect("scanner must eventually produce a token");
5313            if let TokenType::Scalar(_, value) = tok.1 {
5314                assert!(matches!(value, Cow::Borrowed("foo bar")));
5315                break;
5316            }
5317        }
5318    }
5319
5320    #[test]
5321    fn double_quoted_scalar_is_owned_when_escape_sequence_present_for_str_input() {
5322        let mut scanner = Scanner::new(StrInput::new("\"foo\\nbar\"\n"));
5323
5324        loop {
5325            let tok = scanner
5326                .next_token()
5327                .expect("valid YAML must scan without errors")
5328                .expect("scanner must eventually produce a token");
5329            if let TokenType::Scalar(_, value) = tok.1 {
5330                assert!(matches!(value, Cow::Owned(_)));
5331                assert_eq!(&*value, "foo\nbar");
5332                break;
5333            }
5334        }
5335    }
5336
5337    #[test]
5338    fn plain_key_is_borrowed_for_str_input() {
5339        // Keys are just scalars in a key position; they should also be borrowed.
5340        let mut scanner = Scanner::new(StrInput::new("mykey: value\n"));
5341
5342        let mut found_key = false;
5343        let mut key_value: Option<Cow<'_, str>> = None;
5344
5345        loop {
5346            let tok = scanner
5347                .next_token()
5348                .expect("valid YAML must scan without errors");
5349            let Some(tok) = tok else { break };
5350
5351            if matches!(tok.1, TokenType::Key) {
5352                found_key = true;
5353            } else if found_key {
5354                if let TokenType::Scalar(_, value) = tok.1 {
5355                    key_value = Some(value);
5356                    break;
5357                }
5358            }
5359        }
5360
5361        assert!(found_key, "expected to find a Key token");
5362        let key_value = key_value.expect("expected to find a scalar after Key token");
5363        assert!(
5364            matches!(key_value, Cow::Borrowed("mykey")),
5365            "key should be borrowed, got: {key_value:?}"
5366        );
5367    }
5368
5369    #[test]
5370    fn quoted_key_is_borrowed_when_verbatim_for_str_input() {
5371        let mut scanner = Scanner::new(StrInput::new("\"mykey\": value\n"));
5372
5373        let mut found_key = false;
5374        let mut key_value: Option<Cow<'_, str>> = None;
5375
5376        loop {
5377            let tok = scanner
5378                .next_token()
5379                .expect("valid YAML must scan without errors");
5380            let Some(tok) = tok else { break };
5381
5382            if matches!(tok.1, TokenType::Key) {
5383                found_key = true;
5384            } else if found_key {
5385                if let TokenType::Scalar(_, value) = tok.1 {
5386                    key_value = Some(value);
5387                    break;
5388                }
5389            }
5390        }
5391
5392        assert!(found_key, "expected to find a Key token");
5393        let key_value = key_value.expect("expected to find a scalar after Key token");
5394        assert!(
5395            matches!(key_value, Cow::Borrowed("mykey")),
5396            "quoted key should be borrowed when verbatim, got: {key_value:?}"
5397        );
5398    }
5399
5400    #[test]
5401    fn tag_handle_and_suffix_are_borrowed_for_str_input() {
5402        // Test a tag like !!str which should have handle="!!" and suffix="str"
5403        let mut scanner = Scanner::new(StrInput::new("!!str foo\n"));
5404
5405        loop {
5406            let tok = scanner
5407                .next_token()
5408                .expect("valid YAML must scan without errors")
5409                .expect("scanner must eventually produce a token");
5410            if let TokenType::Tag(handle, suffix) = tok.1 {
5411                assert!(
5412                    matches!(handle, Cow::Borrowed("!!")),
5413                    "tag handle should be borrowed, got: {handle:?}"
5414                );
5415                assert!(
5416                    matches!(suffix, Cow::Borrowed("str")),
5417                    "tag suffix should be borrowed, got: {suffix:?}"
5418                );
5419                break;
5420            }
5421        }
5422    }
5423
5424    #[test]
5425    fn local_tag_suffix_is_borrowed_for_str_input() {
5426        // Test a local tag like !mytag which should have handle="!" and suffix="mytag"
5427        let mut scanner = Scanner::new(StrInput::new("!mytag foo\n"));
5428
5429        loop {
5430            let tok = scanner
5431                .next_token()
5432                .expect("valid YAML must scan without errors")
5433                .expect("scanner must eventually produce a token");
5434            if let TokenType::Tag(handle, suffix) = tok.1 {
5435                assert!(
5436                    matches!(handle, Cow::Borrowed("!")),
5437                    "local tag handle should be '!', got: {handle:?}"
5438                );
5439                assert!(
5440                    matches!(suffix, Cow::Borrowed("mytag")),
5441                    "local tag suffix should be borrowed, got: {suffix:?}"
5442                );
5443                break;
5444            }
5445        }
5446    }
5447
5448    #[test]
5449    fn local_tag_suffix_with_punctuation_is_borrowed_for_str_input() {
5450        let mut scanner = Scanner::new(StrInput::new("!mytag/part foo\n"));
5451
5452        loop {
5453            let tok = scanner
5454                .next_token()
5455                .expect("valid YAML must scan without errors")
5456                .expect("scanner must eventually produce a token");
5457            if let TokenType::Tag(handle, suffix) = tok.1 {
5458                assert!(matches!(handle, Cow::Borrowed("!")));
5459                assert!(matches!(suffix, Cow::Borrowed("mytag/part")));
5460                break;
5461            }
5462        }
5463    }
5464
5465    #[test]
5466    fn tag_with_uri_escape_is_owned_for_str_input() {
5467        // Test a tag with URI escape like !my%20tag - suffix must be owned due to decoding
5468        let mut scanner = Scanner::new(StrInput::new("!!my%20tag foo\n"));
5469
5470        loop {
5471            let tok = scanner
5472                .next_token()
5473                .expect("valid YAML must scan without errors")
5474                .expect("scanner must eventually produce a token");
5475            if let TokenType::Tag(handle, suffix) = tok.1 {
5476                assert!(
5477                    matches!(handle, Cow::Borrowed("!!")),
5478                    "tag handle should still be borrowed, got: {handle:?}"
5479                );
5480                assert!(
5481                    matches!(suffix, Cow::Owned(_)),
5482                    "tag suffix with URI escape should be owned, got: {suffix:?}"
5483                );
5484                assert_eq!(&*suffix, "my tag");
5485                break;
5486            }
5487        }
5488    }
5489
5490    #[test]
5491    fn flow_scalar_buffer_tracks_pending_whitespace() {
5492        let mut borrowed = super::FlowScalarBuf::new_borrowed(2);
5493
5494        borrowed.note_pending_ws(5, 8);
5495        borrowed.commit_pending_ws();
5496        assert!(matches!(
5497            borrowed,
5498            super::FlowScalarBuf::Borrowed {
5499                end: 8,
5500                pending_ws_start: None,
5501                pending_ws_end: 8,
5502                ..
5503            }
5504        ));
5505
5506        borrowed.note_pending_ws(9, 11);
5507        borrowed.discard_pending_ws();
5508        assert!(matches!(
5509            borrowed,
5510            super::FlowScalarBuf::Borrowed {
5511                end: 8,
5512                pending_ws_start: None,
5513                pending_ws_end: 8,
5514                ..
5515            }
5516        ));
5517        assert!(borrowed.as_owned_mut().is_none());
5518
5519        let mut owned = super::FlowScalarBuf::new_owned();
5520        owned.as_owned_mut().unwrap().push_str("owned");
5521        assert!(matches!(owned, super::FlowScalarBuf::Owned(ref s) if s == "owned"));
5522    }
5523
5524    fn first_scanner_error_kind(input: &str) -> ErrorKind {
5525        first_scanner_error(input).kind().clone()
5526    }
5527
5528    fn first_buffered_scanner_error_kind(input: &str) -> ErrorKind {
5529        let mut scanner = Scanner::new(BufferedInput::new(input.chars()));
5530        loop {
5531            match scanner.next_token() {
5532                Ok(Some(_)) => {}
5533                Ok(None) => panic!("expected scanner error"),
5534                Err(error) => return error.kind().clone(),
5535            }
5536        }
5537    }
5538
5539    fn first_scanner_error(input: &str) -> ScanError {
5540        let mut scanner = Scanner::new(StrInput::new(input));
5541        loop {
5542            match scanner.next_token() {
5543                Ok(Some(_)) => {}
5544                Ok(None) => panic!("expected scanner error"),
5545                Err(error) => return error,
5546            }
5547        }
5548    }
5549
5550    fn first_scalar_value(input: &str) -> String {
5551        let mut scanner = Scanner::new(StrInput::new(input));
5552        loop {
5553            match scanner.next_token().expect("scanner should not error") {
5554                Some(Token(_, TokenType::Scalar(_, value))) => return value.into_owned(),
5555                Some(_) => {}
5556                None => panic!("expected scalar token"),
5557            }
5558        }
5559    }
5560
5561    fn first_buffered_scalar_value(input: &str) -> String {
5562        let mut scanner = Scanner::new(BufferedInput::new(input.chars()));
5563        loop {
5564            match scanner.next_token().expect("scanner should not error") {
5565                Some(Token(_, TokenType::Scalar(_, value))) => return value.into_owned(),
5566                Some(_) => {}
5567                None => panic!("expected scalar token"),
5568            }
5569        }
5570    }
5571
5572    #[test]
5573    fn iterator_next_emits_error_and_then_stays_empty() {
5574        let mut scanner = Scanner::new(StrInput::new("\"unterminated"));
5575
5576        let error = scanner
5577            .by_ref()
5578            .find_map(Result::err)
5579            .expect("scanner should emit the error");
5580        assert_eq!(error.kind(), &ErrorKind::UnclosedQuotedScalar);
5581        assert!(scanner.next().is_none());
5582    }
5583
5584    #[test]
5585    fn next_token_returns_none_after_stream_end() {
5586        let mut scanner = Scanner::new(StrInput::new(""));
5587
5588        while let Some(token) = scanner.next_token().unwrap() {
5589            if matches!(token.1, TokenType::StreamEnd) {
5590                break;
5591            }
5592        }
5593
5594        assert!(scanner.stream_started());
5595        assert!(scanner.stream_ended());
5596        assert!(scanner.next_token().unwrap().is_none());
5597    }
5598
5599    #[test]
5600    fn directive_name_must_be_present() {
5601        assert_eq!(
5602            first_scanner_error_kind("%\n"),
5603            ErrorKind::MissingDirectiveName
5604        );
5605    }
5606
5607    #[test]
5608    fn yaml_directive_requires_dot_between_version_numbers() {
5609        assert_eq!(
5610            first_scanner_error_kind("%YAML 1\n"),
5611            ErrorKind::MissingYamlVersionSeparator
5612        );
5613    }
5614
5615    #[test]
5616    fn yaml_directive_requires_major_version_number() {
5617        assert_eq!(
5618            first_scanner_error_kind("%YAML .2\n"),
5619            ErrorKind::MissingYamlVersion
5620        );
5621    }
5622
5623    #[test]
5624    fn yaml_directive_rejects_extremely_long_version_number() {
5625        assert_eq!(
5626            first_scanner_error_kind("%YAML 1234567890.2\n"),
5627            ErrorKind::YamlVersionTooLong
5628        );
5629    }
5630
5631    #[test]
5632    fn tag_directive_handle_must_end_with_bang() {
5633        assert_eq!(
5634            first_scanner_error_kind("%TAG !bad tag:example.com,2024:\n"),
5635            ErrorKind::ExpectedTagDirectiveBang
5636        );
5637    }
5638
5639    #[test]
5640    fn tag_directive_handle_must_start_with_bang() {
5641        assert_eq!(
5642            first_scanner_error_kind("%TAG bad! tag:example.com,2024:\n"),
5643            ErrorKind::ExpectedTagBang
5644        );
5645        assert_eq!(
5646            first_buffered_scanner_error_kind("%TAG bad! tag:example.com,2024:\n"),
5647            ErrorKind::ExpectedTagBang
5648        );
5649    }
5650
5651    #[test]
5652    fn tag_directive_prefix_must_start_with_tag_character() {
5653        assert_eq!(
5654            first_scanner_error_kind("%TAG !e! `bad\n"),
5655            ErrorKind::InvalidGlobalTagCharacter
5656        );
5657    }
5658
5659    #[test]
5660    fn tag_directive_prefix_must_end_before_invalid_content() {
5661        assert_eq!(
5662            first_scanner_error_kind("%TAG !e! tag:example.com^suffix\n"),
5663            ErrorKind::InvalidTagDirectiveTerminator
5664        );
5665    }
5666
5667    #[test]
5668    fn tag_directive_prefix_with_uri_escape_is_owned_and_decoded() {
5669        let mut scanner =
5670            Scanner::new(StrInput::new("%TAG !e! tag:example.com,2024:some%20app/\n"));
5671
5672        loop {
5673            let token = scanner
5674                .next_token()
5675                .expect("valid directive should scan")
5676                .expect("scanner must produce a directive token");
5677            if let TokenType::TagDirective(handle, prefix) = token.1 {
5678                assert!(matches!(handle, Cow::Borrowed("!e!")));
5679                assert!(matches!(prefix, Cow::Owned(_)));
5680                assert_eq!(&*prefix, "tag:example.com,2024:some app/");
5681                break;
5682            }
5683        }
5684    }
5685
5686    #[test]
5687    fn bare_bang_tag_scans_as_non_specific_tag() {
5688        let mut scanner = Scanner::new(StrInput::new("! foo\n"));
5689
5690        loop {
5691            let token = scanner
5692                .next_token()
5693                .expect("valid tag should scan")
5694                .expect("scanner must produce a tag token");
5695            if let TokenType::Tag(handle, suffix) = token.1 {
5696                assert_eq!(&*handle, "");
5697                assert_eq!(&*suffix, "!");
5698                break;
5699            }
5700        }
5701    }
5702
5703    #[test]
5704    fn tag_requires_separation_after_suffix() {
5705        assert_eq!(
5706            first_scanner_error_kind("!foo,bar\n"),
5707            ErrorKind::InvalidTagTerminator
5708        );
5709    }
5710
5711    #[test]
5712    fn verbatim_tag_requires_uri() {
5713        assert_eq!(
5714            first_scanner_error_kind("!<> foo\n"),
5715            ErrorKind::MissingTagUri
5716        );
5717    }
5718
5719    #[test]
5720    fn verbatim_tag_requires_closing_angle_bracket() {
5721        assert_eq!(
5722            first_scanner_error_kind("!<tag:yaml.org,2002:str foo\n"),
5723            ErrorKind::UnclosedVerbatimTag
5724        );
5725    }
5726
5727    #[test]
5728    fn tag_uri_escape_requires_hex_digits() {
5729        assert_eq!(
5730            first_scanner_error_kind("!!bad%zz foo\n"),
5731            ErrorKind::InvalidTagEscape
5732        );
5733    }
5734
5735    #[test]
5736    fn tag_uri_escape_rejects_bad_leading_utf8_byte() {
5737        assert_eq!(
5738            first_scanner_error_kind("!!bad%80 foo\n"),
5739            ErrorKind::InvalidTagUtf8LeadingByte
5740        );
5741    }
5742
5743    #[test]
5744    fn tag_uri_escape_rejects_bad_trailing_utf8_byte() {
5745        assert_eq!(
5746            first_scanner_error_kind("!!bad%C2%41 foo\n"),
5747            ErrorKind::InvalidTagUtf8TrailingByte
5748        );
5749    }
5750
5751    #[test]
5752    fn tag_uri_escape_rejects_invalid_utf8_codepoint() {
5753        assert_eq!(
5754            first_scanner_error_kind("!!bad%F4%90%80%80 foo\n"),
5755            ErrorKind::InvalidTagUtf8
5756        );
5757    }
5758
5759    #[test]
5760    fn anchors_and_aliases_require_names() {
5761        assert_eq!(
5762            first_scanner_error_kind("& \n"),
5763            ErrorKind::MissingAnchorOrAliasName
5764        );
5765        assert_eq!(
5766            first_scanner_error_kind("* \n"),
5767            ErrorKind::MissingAnchorOrAliasName
5768        );
5769    }
5770
5771    #[test]
5772    fn document_end_marker_rejects_trailing_content() {
5773        assert_eq!(
5774            first_scanner_error_kind("... trailing\n"),
5775            ErrorKind::InvalidDocumentEnd
5776        );
5777    }
5778
5779    #[test]
5780    fn reserved_indicators_are_rejected_outside_directives() {
5781        let error = first_scanner_error(" @\n");
5782
5783        assert_eq!(
5784            error.kind(),
5785            &ErrorKind::UnexpectedCharacter { character: '@' }
5786        );
5787    }
5788
5789    #[test]
5790    fn flow_block_entry_indicator_is_rejected() {
5791        assert_eq!(
5792            first_scanner_error_kind("[- ]\n"),
5793            ErrorKind::BlockEntryInFlowCollection
5794        );
5795    }
5796
5797    #[test]
5798    fn block_entry_after_tabbed_separator_reports_specific_error() {
5799        assert_eq!(
5800            first_scanner_error_kind("-\t- value\n"),
5801            ErrorKind::InvalidBlockEntryWhitespace
5802        );
5803    }
5804
5805    #[test]
5806    fn document_indicator_reports_unclosed_flow_collection() {
5807        let error = first_scanner_error("[\n---\n");
5808
5809        assert_eq!(
5810            error.kind(),
5811            &ErrorKind::UnclosedFlowCollection { open: '[' }
5812        );
5813    }
5814
5815    #[test]
5816    fn block_scalar_header_rejects_trailing_content() {
5817        assert_eq!(
5818            first_scanner_error_kind("|+ trailing\n"),
5819            ErrorKind::InvalidBlockScalarHeader
5820        );
5821    }
5822
5823    #[test]
5824    fn block_scalar_rejects_zero_indent_indicator() {
5825        assert_eq!(
5826            first_scanner_error_kind("|0\n"),
5827            ErrorKind::ZeroBlockScalarIndent
5828        );
5829        assert_eq!(
5830            first_scanner_error_kind("|+0\n"),
5831            ErrorKind::ZeroBlockScalarIndent
5832        );
5833    }
5834
5835    #[test]
5836    fn empty_block_scalar_at_eof_honors_chomping() {
5837        assert_eq!(first_scalar_value("|\n"), "");
5838        assert_eq!(first_scalar_value("|-\n"), "");
5839        assert_eq!(first_scalar_value("|+\n"), "");
5840        assert_eq!(first_scalar_value("|+\n\n"), "\n");
5841        assert_eq!(first_scalar_value("|+\n   "), "\n");
5842    }
5843
5844    #[test]
5845    fn buffered_block_scalar_reads_content_past_lookahead_window() {
5846        assert_eq!(
5847            first_buffered_scalar_value("|\n  abcdefghijklmnopqrstuvwxyz\n"),
5848            "abcdefghijklmnopqrstuvwxyz\n"
5849        );
5850    }
5851
5852    #[test]
5853    fn explicit_indent_block_scalar_can_end_at_document_marker() {
5854        assert_eq!(first_scalar_value("|1\n...\n"), "");
5855    }
5856
5857    #[test]
5858    fn root_explicit_indent_block_scalar_rejects_underindented_content() {
5859        assert_eq!(
5860            first_scanner_error_kind("|2\nx\n"),
5861            ErrorKind::InvalidBlockScalarIndent
5862        );
5863    }
5864
5865    #[test]
5866    fn quoted_scalar_rejects_document_indicator_at_line_start() {
5867        assert_eq!(
5868            first_scanner_error_kind("\"one\n---\ntwo\"\n"),
5869            ErrorKind::DocumentIndicatorInQuotedScalar
5870        );
5871    }
5872
5873    #[test]
5874    fn quoted_scalar_rejects_tab_indentation_after_line_break() {
5875        assert_eq!(
5876            first_scanner_error_kind("a: \"one\n\tbad\"\n"),
5877            ErrorKind::TabInIndentation
5878        );
5879    }
5880
5881    #[test]
5882    fn quoted_scalar_rejects_underindented_continuation() {
5883        assert_eq!(
5884            first_scanner_error_kind("a: \"one\nbad\"\n"),
5885            ErrorKind::InvalidQuotedScalarIndent
5886        );
5887    }
5888
5889    #[test]
5890    fn quoted_scalar_trailing_content_error_names_quote_style() {
5891        assert_eq!(
5892            first_scanner_error_kind("'foo' trailing\n"),
5893            ErrorKind::InvalidTrailingSingleQuotedScalar
5894        );
5895        assert_eq!(
5896            first_scanner_error_kind("\"foo\" trailing\n"),
5897            ErrorKind::InvalidTrailingDoubleQuotedScalar
5898        );
5899    }
5900
5901    #[test]
5902    fn quoted_scalar_escape_errors_cover_hex_and_surrogate_edges() {
5903        assert_eq!(
5904            first_scanner_error_kind("\"\\xG0\"\n"),
5905            ErrorKind::InvalidQuotedScalarHexEscape
5906        );
5907        assert_eq!(
5908            first_scanner_error_kind("\"\\uD800\\uGGGG\"\n"),
5909            ErrorKind::InvalidLowSurrogateHexEscape
5910        );
5911        assert_eq!(
5912            first_scanner_error_kind("\"\\uD800\\u0041\"\n"),
5913            ErrorKind::InvalidLowSurrogate
5914        );
5915        assert_eq!(
5916            first_scanner_error_kind("\"\\U00110000\"\n"),
5917            ErrorKind::InvalidUnicodeEscape
5918        );
5919    }
5920
5921    #[test]
5922    fn under_indented_flow_scalar_is_accepted() {
5923        let tokens: Vec<_> = Scanner::new(StrInput::new("a:\n  [\nfoo]\n"))
5924            .map(|result| result.expect("under-indented flow entry should scan").1)
5925            .collect();
5926        assert_eq!(
5927            tokens,
5928            [
5929                TokenType::StreamStart,
5930                TokenType::BlockMappingStart,
5931                TokenType::Key,
5932                TokenType::Scalar(ScalarStyle::Plain, "a".into()),
5933                TokenType::Value,
5934                TokenType::FlowSequenceStart,
5935                TokenType::Scalar(ScalarStyle::Plain, "foo".into()),
5936                TokenType::FlowSequenceEnd,
5937                TokenType::BlockEnd,
5938                TokenType::StreamEnd,
5939            ]
5940        );
5941    }
5942
5943    #[test]
5944    fn required_simple_key_requires_value_at_stream_end() {
5945        let error = first_scanner_error("a:\n&b\n- c\n");
5946
5947        assert_eq!(error.kind(), &ErrorKind::SimpleKeyExpected);
5948        assert_eq!(error.marker().index(), 3);
5949        assert_eq!(error.marker().line(), 2);
5950        assert_eq!(error.marker().col(), 0);
5951    }
5952
5953    #[test]
5954    fn plain_scalar_rejects_dash_before_flow_indicator() {
5955        assert_eq!(
5956            first_scanner_error_kind("[-]\n"),
5957            ErrorKind::PlainScalarStartsWithDashFlowIndicator
5958        );
5959    }
5960
5961    #[test]
5962    fn explicit_key_rejects_tab_after_indicator() {
5963        assert_eq!(
5964            first_scanner_error_kind("? \tfoo\n"),
5965            ErrorKind::TabNotAllowed
5966        );
5967    }
5968
5969    #[test]
5970    fn flow_mapping_rejects_adjacent_collection_value_after_plain_key() {
5971        assert_eq!(
5972            first_scanner_error_kind("[a:[]]\n"),
5973            ErrorKind::FlowMappingValueAdjacentCollection
5974        );
5975    }
5976
5977    #[test]
5978    fn implicit_flow_mapping_colon_cannot_move_to_next_line() {
5979        assert_eq!(
5980            first_scanner_error_kind("[foo\n: bar]\n"),
5981            ErrorKind::InvalidColonPlacement
5982        );
5983    }
5984
5985    #[test]
5986    fn invalid_simple_key_token_positions_are_scan_errors() {
5987        for (tokens_parsed, token_number) in [(1, 0), (0, 1)] {
5988            let mut scanner = Scanner::new(StrInput::new(": value\n"));
5989            scanner.fetch_stream_start();
5990            scanner.tokens.clear();
5991            scanner.tokens_parsed = tokens_parsed;
5992
5993            let simple_key = scanner
5994                .simple_keys
5995                .last_mut()
5996                .expect("stream start should create a simple key slot");
5997            simple_key.possible = true;
5998            simple_key.token_number = token_number;
5999
6000            let error = scanner
6001                .fetch_value()
6002                .expect_err("invalid simple key position should be reported as a scan error");
6003            assert_eq!(error.kind(), &ErrorKind::InvalidSimpleKey);
6004            assert_eq!(error.marker(), &Marker::new(0, 1, 0));
6005        }
6006    }
6007
6008    #[test]
6009    fn issue14_alias_scanner_consumes_colon_as_name_character() {
6010        let mut scanner = Scanner::new(StrInput::new("*foo: bar\n"));
6011
6012        assert!(matches!(
6013            scanner.next_token().unwrap().unwrap().1,
6014            TokenType::StreamStart
6015        ));
6016
6017        let token = scanner.next_token().unwrap().unwrap();
6018
6019        assert!(
6020            matches!(token.1, TokenType::Alias(ref name) if name.as_ref() == "foo:"),
6021            "expected `*foo: bar` to start with Alias(\"foo:\"), got {token:?}"
6022        );
6023    }
6024
6025    #[test]
6026    fn issue14_anchor_scanner_consumes_colon_as_name_character() {
6027        let mut scanner = Scanner::new(StrInput::new("&foo: bar\n"));
6028
6029        assert!(matches!(
6030            scanner.next_token().unwrap().unwrap().1,
6031            TokenType::StreamStart
6032        ));
6033
6034        let token = scanner.next_token().unwrap().unwrap();
6035
6036        assert!(
6037            matches!(token.1, TokenType::Anchor(ref name) if name.as_ref() == "foo:"),
6038            "expected `&foo: bar` to start with Anchor(\"foo:\"), got {token:?}"
6039        );
6040    }
6041}