yary/scanner/scalar/
block.rs

1/*
2 * This Source Code Form is subject to the terms of the
3 * Mozilla Public License, v. 2.0. If a copy of the MPL
4 * was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7//! This module contains the functions responsible for
8//! scanning block scalars into Tokens.
9//!
10//! It exports 3 functions:
11//!
12//! - scan_block_scalar
13//! - scan_block_scalar_eager
14//! - scan_block_scalar_lazy
15//!
16//! The eager variant produces a scalar Token (or an error)
17//! that may allocate and performs any processing the YAML
18//! spec requires. The lazy variant instead defers any
19//! processing, returning a structure that can process the
20//! scalar at a later time.
21//!
22//! scan_block_scalar provides the top level interface of
23//! this functionality.
24//!
25//! Two further functions are notable: scan_indent and
26//! scan_chomp.
27//!
28//! scan_indent handles the scanning of scalar indentation,
29//! and will typically be called once per line in a block
30//! scalar. It is also indirectly responsible for
31//! terminating the main loop which relies on the local
32//! indent level that scan_indent sets.
33//!
34//! scan_chomp finishes the scalar scanning, and is
35//! responsible for ensuring the correct amount of
36//! trailing whitespace is added to the scalar based on its
37//! chomp header -- the '|' or '>'.
38
39use std::num::NonZeroU8;
40
41use atoi::atoi;
42
43use crate::{
44    scanner::{
45        context::Context,
46        entry::MaybeToken,
47        error::{ScanError, ScanResult as Result},
48        flag::{Flags, O_LAZY},
49        scalar::as_maybe,
50        stats::MStats,
51    },
52    token::{ScalarStyle, Slice, Token},
53};
54
55/// Scans a block scalar returning an opaque handle to a
56/// byte slice that could be a valid scalar.
57///
58/// This function is a wrapper around
59/// scan_block_scalar_eager and scan_block_scalar_lazy. See
60/// the respective documentation for an explanation.
61pub(in crate::scanner) fn scan_block_scalar<'de>(
62    opts: Flags,
63    base: &'de str,
64    stats: &mut MStats,
65    cxt: &Context,
66    fold: bool,
67) -> Result<(MaybeToken<'de>, usize)>
68{
69    // It is safe to assume our indentation level is >=0 here
70    // because block scalars can only occur as mapping or
71    // sequence values
72    let indent = cxt.indent().as_usize();
73
74    match opts.contains(O_LAZY)
75    {
76        true => scan_block_scalar_lazy(opts, base, stats, indent, fold).map(as_maybe),
77        false => scan_block_scalar_eager(opts, base, stats, indent, fold).map(as_maybe),
78    }
79}
80/// Scans a block scalar, returning a Token and the amount
81/// read from .base. This function will attempt to borrow
82/// from .base, however the circumstances in which it
83/// remains possible to borrow are narrow.
84///
85/// See:
86///     YAML 1.2: Section 8.1
87///     yaml.org/spec/1.2/#c-b-block-header(m,t)
88pub(in crate::scanner) fn scan_block_scalar_eager<'de>(
89    opts: Flags,
90    base: &'de str,
91    stats: &mut MStats,
92    base_indent: usize,
93    fold: bool,
94) -> Result<(Token<'de>, usize)>
95{
96    // Initialize the local state handlers
97    let mut buffer = base;
98    let mut scratch = Vec::new();
99    let mut local_stats = stats.clone();
100
101    // Tracks if a borrow is possible from the underlying .base
102    let mut can_borrow = true;
103
104    // Tracks the start and end of the scalar content. Note that
105    // these track two different values depending on whether
106    // we .can_borrow. If we can, acts as start/end indexes
107    // into .base, otherwise as start/end indexes into
108    // .scratch. These can be difficult to keep track of; so
109    // pay attention to the context before setting them.
110    let mut content_start: usize = 0;
111    let mut content_end: usize = 0;
112
113    // Keeps track of the outstanding lines that need to be
114    // reconciled
115    let mut lines: usize = 0;
116
117    // The indentation level of this scalar
118    let indent: usize;
119
120    // Scalar style mapping
121    let style = match fold
122    {
123        true => ScalarStyle::Folded,
124        false => ScalarStyle::Literal,
125    };
126
127    // Eat the '|' or '>'
128    cache!(~buffer, 1, opts)?;
129    advance!(buffer, :local_stats, 1);
130
131    // Calculate any headers this scalar may have
132    let (chomp, explicit) = scan_headers(opts, &mut buffer, &mut local_stats)?;
133
134    // The header line must contain nothing after the headers
135    // excluding a comment until the line ending
136    skip_blanks(opts, &mut buffer, &mut local_stats, COMMENTS)?;
137    cache!(~buffer, 1, opts)?;
138    if !isWhiteSpaceZ!(~buffer)
139    {
140        return Err(ScanError::InvalidBlockScalar);
141    }
142
143    // Eat the line break
144    advance!(buffer, :local_stats, @line);
145
146    // Set the indent explicitly if defined, otherwise detect
147    // from the indentation level
148    match explicit.map(NonZeroU8::get)
149    {
150        Some(explicit) => indent = base_indent + explicit as usize,
151        None =>
152        {
153            indent = detect_indent_level(
154                opts,
155                &mut buffer,
156                &mut local_stats,
157                base_indent,
158                &mut lines,
159                &mut can_borrow,
160            )?
161        },
162    }
163
164    // Add any preceding lines to the tracked borrow or scratch
165    // space
166    match can_borrow
167    {
168        true => content_start = local_stats.read - stats.read,
169        false =>
170        {
171            for _ in 0..lines
172            {
173                scratch.push(NEWLINE)
174            }
175        },
176    }
177
178    lines = 0;
179
180    // Loop over scalar line by line until we reach a less
181    // indented line or EOF
182    while local_stats.column == indent && (!buffer.is_empty())
183    {
184        /*
185         * We're at the start of an indented line
186         */
187
188        // Trapdoor to alloc land. Unfortunately, block scalars are
189        // very unforgiving in which cases can be borrowed.
190        // Basically limiting us to the unusual case of a single
191        // line directly following the header line. E.g:
192        //
193        //  key: |  # or >
194        //    I can be borrowed
195        if can_borrow && lines > 0
196        {
197            scratch.extend_from_slice(base[content_start..content_end].as_bytes());
198
199            can_borrow = false
200        }
201
202        // If its a folding ('>') block scalar
203        if fold
204        {
205            // Handle line joins as needed
206            match lines
207            {
208                // No join needed, we're done
209                0 =>
210                {},
211                // If a single line was recorded, we _cannot_ have seen a line wholly made of
212                // whitespace, therefore join via a space
213                1 =>
214                {
215                    scratch.push(SPACE);
216                },
217                // Else we need to append (n - 1) newlines, as we skip the origin line's break
218                n =>
219                {
220                    // Safety: we can only reach this branch if n > 1
221                    for _ in 0..n - 1
222                    {
223                        scratch.push(NEWLINE)
224                    }
225                },
226            }
227        }
228        // Otherwise simply append the collected newlines literally ('|')
229        else
230        {
231            for _ in 0..lines
232            {
233                scratch.push(NEWLINE)
234            }
235        }
236
237        // Reset line counter for next iteration
238        lines = 0;
239
240        // Mark content start
241        match can_borrow
242        {
243            true =>
244            {
245                if content_start == 0
246                {
247                    content_start = local_stats.read - stats.read
248                }
249            },
250            false => content_start = 0,
251        }
252
253        // Eat the line's content until the line break (or EOF)
254        cache!(~buffer, 1, opts)?;
255        while !isBreakZ!(~buffer)
256        {
257            cache!(~buffer, 1, opts)?;
258
259            if !can_borrow
260            {
261                scratch.push(buffer.as_bytes()[0])
262            }
263            advance!(buffer, :local_stats, 1);
264        }
265
266        // Mark content end
267        match can_borrow
268        {
269            true => content_end = local_stats.read - stats.read,
270            false => content_end = scratch.len(),
271        }
272
273        // Eat the line break (if not EOF)
274        cache!(~buffer, 1, opts)?;
275        if isBreak!(~buffer)
276        {
277            advance!(buffer, :local_stats, @line);
278            lines += 1;
279        }
280
281        // Chomp indentation until the next indented line
282        scan_indent(
283            opts,
284            &mut buffer,
285            &mut local_stats,
286            &mut lines,
287            &mut can_borrow,
288            indent,
289        )?;
290    }
291
292    // Scan the ending whitespace, returning the final scalar
293    let c_params = ChompParams::new(chomp, content_start, content_end, lines);
294    let scalar = scan_chomp(base, scratch, &mut can_borrow, c_params)?;
295
296    *stats = local_stats;
297    let advance = base.len() - buffer.len();
298    let token = Token::Scalar(scalar, style);
299
300    Ok((token, advance))
301}
302
303pub(in crate::scanner) fn scan_block_scalar_lazy<'de>(
304    opts: Flags,
305    base: &'de str,
306    stats: &mut MStats,
307    base_indent: usize,
308    fold: bool,
309) -> Result<(Deferred<'de>, usize)>
310{
311    // Initialize the local state handlers
312    let mut buffer = base;
313    let mut local_stats = stats.clone();
314
315    // The indentation level of this scalar
316    let indent: usize;
317
318    // Eat the '|' or '>'
319    cache!(~buffer, 1, opts)?;
320    advance!(buffer, :local_stats, 1);
321
322    // Calculate any headers this scalar may have
323    let (_, explicit) = scan_headers(opts, &mut buffer, &mut local_stats)?;
324
325    // The header line must contain nothing after the headers
326    // excluding a comment until the line ending
327    skip_blanks(opts, &mut buffer, &mut local_stats, COMMENTS)?;
328    cache!(~buffer, 1, opts)?;
329    if !isWhiteSpaceZ!(~buffer)
330    {
331        return Err(ScanError::InvalidBlockScalar);
332    }
333
334    // Eat the line break
335    advance!(buffer, :local_stats, @line);
336
337    // Set the indent explicitly if defined, otherwise detect
338    // from the indentation level
339    match explicit.map(NonZeroU8::get)
340    {
341        Some(explicit) => indent = base_indent + explicit as usize,
342        None =>
343        {
344            indent = detect_indent_level(
345                opts,
346                &mut buffer,
347                &mut local_stats,
348                base_indent,
349                &mut 0,
350                &mut false,
351            )?
352        },
353    }
354
355    while local_stats.column == indent && (!buffer.is_empty())
356    {
357        /*
358         * We're at the start of an indented line
359         */
360
361        // Eat the line's content until the line break (or EOF)
362        cache!(~buffer, 1, opts)?;
363        while !isBreakZ!(~buffer)
364        {
365            cache!(~buffer, 1, opts)?;
366            advance!(buffer, :local_stats, 1);
367        }
368
369        // Eat the line break (if not EOF)
370        cache!(~buffer, 1, opts)?;
371        if isBreak!(~buffer)
372        {
373            advance!(buffer, :local_stats, @line);
374        }
375
376        // Chomp indentation until the next indented line
377        scan_indent(
378            opts,
379            &mut buffer,
380            &mut local_stats,
381            &mut 0,
382            &mut false,
383            indent,
384        )?;
385    }
386
387    let advance = base.len() - buffer.len();
388    let slice = &base[..advance];
389
390    let lazy = Deferred::new(opts, slice, stats.clone(), base_indent, fold);
391
392    *stats = local_stats;
393
394    Ok((lazy, advance))
395}
396
397/// Retrieve a block scalar's headers
398fn scan_headers(
399    opts: Flags,
400    buffer: &mut &str,
401    stats: &mut MStats,
402) -> Result<(ChompStyle, IndentHeader)>
403{
404    let mut skip = 0;
405    let mut indent = None;
406    let mut chomp = ChompStyle::Clip;
407
408    cache!(~buffer, 2, opts)?;
409
410    // Set the explicit indent if it exists.
411    //
412    // Note that we silently eat an invalid indent (0) rather
413    // than erroring
414    match buffer.as_bytes()
415    {
416        [i @ b'0'..=b'9', ..] | [_, i @ b'0'..=b'9', ..] =>
417        {
418            indent = atoi::<u8>(&[*i]).and_then(NonZeroU8::new);
419            skip += 1;
420        },
421        _ =>
422        {},
423    }
424
425    // Set the chomping behavior of the scalar, if required
426    match buffer.as_bytes()
427    {
428        [c, ..] | [_, c, ..] if matches!(*c, b'+') =>
429        {
430            chomp = ChompStyle::Keep;
431            skip += 1;
432        },
433        [c, ..] | [_, c, ..] if matches!(*c, b'-') =>
434        {
435            chomp = ChompStyle::Strip;
436            skip += 1;
437        },
438        _ =>
439        {},
440    }
441
442    advance!(*buffer, :stats, skip);
443
444    Ok((chomp, indent))
445}
446
447/// Chomp the indentation spaces of a block scalar
448fn scan_indent(
449    opts: Flags,
450    buffer: &mut &str,
451    stats: &mut MStats,
452    lines: &mut usize,
453    _can_borrow: &mut bool,
454    indent: usize,
455) -> Result<bool>
456{
457    if stats.column >= indent
458    {
459        return Ok(false);
460    }
461
462    cache!(~buffer, 1, opts)?;
463
464    while stats.column < indent && isWhiteSpace!(~buffer)
465    {
466        // Indentation space, chomp
467        if check!(~buffer => b' ')
468        {
469            advance!(*buffer, :stats, 1);
470        }
471        // Tabs in indentation, error
472        else if check!(~buffer => b'\t')
473        {
474            return Err(ScanError::InvalidTab);
475        }
476        // Line break, chomp; increment lines
477        else if isBreak!(~buffer)
478        {
479            *lines += 1;
480            advance!(*buffer, :stats, @line);
481        }
482
483        cache!(~buffer, 1, opts)?;
484    }
485
486    Ok(true)
487}
488
489/// Process a block scalar's ending whitespace according to
490/// the YAML Spec section 8.1.1.2.
491///
492/// See:
493///     yaml.org/spec/1.2/#c-chomping-indicator(t)
494fn scan_chomp<'de>(
495    base: &'de str,
496    mut scratch: Vec<u8>,
497    can_borrow: &mut bool,
498    params: ChompParams,
499) -> Result<Slice<'de>>
500{
501    let mut scalar = cow!("");
502    let ChompParams {
503        style,
504        start,
505        mut end,
506        mut lines,
507    } = params;
508
509    if *can_borrow
510    {
511        match style
512        {
513            // Clip the scalar to 0 or 1 line break
514            ChompStyle::Clip =>
515            {
516                // Check if we had trailing lines, extending the borrow to
517                // include the first
518                if lines > 0
519                {
520                    end += widthOf!(~base[end..], 1);
521                }
522
523                scalar = cow!(&base[start..end])
524            },
525            // Ignore any trailing line breaks just returning the scalar
526            ChompStyle::Strip => scalar = cow!(&base[start..end]),
527            // We only maintain the borrow if there is 0 or 1 new lines
528            //
529            // Technically, we could extend the logic here to check if the most recent scan_indent
530            // didn't skip any spaces (only line breaks).
531            ChompStyle::Keep => match lines
532            {
533                n @ 0 | n @ 1 => scalar = cow!(&base[start..end + n]),
534                // The only way to hit this branch is if the scalar could still be borrowed, and
535                // thus is a single line. In this case we have to copy the borrow to the scratch
536                // space, and append any trailing lines the previous scan_indent produced.
537                //
538                // Note that .start and .end in this case refer to .base offsets _AND NOT_ scratch
539                // offsets. Care must be taken to ensure we _NEVER_ index into scratch with them
540                // when .style == Keep
541                _ =>
542                {
543                    scratch.extend_from_slice(base[start..end].as_bytes());
544
545                    while lines > 0
546                    {
547                        scratch.push(NEWLINE);
548                        lines -= 1;
549                    }
550
551                    // Ensure we hit the copy branch below so as to correctly
552                    // handle .scratch -> .scalar transform
553                    *can_borrow = false;
554                },
555            },
556        }
557    }
558
559    if !*can_borrow
560    {
561        match style
562        {
563            // Clip the trailing line breaks to 0 or 1, appending as necessary
564            ChompStyle::Clip =>
565            {
566                if lines > 0
567                {
568                    scratch.push(NEWLINE);
569                    end += 1;
570                }
571
572                scratch.truncate(end);
573            },
574            // Return the content as is, no trailing whitespace
575            ChompStyle::Strip => scratch.truncate(end),
576            // Append any trailing line breaks that weren't caught in the main loop of
577            // scan_block_scalar
578            ChompStyle::Keep =>
579            {
580                for _ in 0..lines
581                {
582                    scratch.push(NEWLINE)
583                }
584            },
585        }
586
587        scalar = cow!(String::from_utf8(scratch).unwrap());
588    }
589
590    Ok(scalar)
591}
592
593/// Auto-detect the indentation level from the first non
594/// header line of a block scalar
595fn detect_indent_level(
596    opts: Flags,
597    buffer: &mut &str,
598    stats: &mut MStats,
599    base_indent: usize,
600    lines: &mut usize,
601    can_borrow: &mut bool,
602) -> Result<usize>
603{
604    let mut indent = 0;
605
606    loop
607    {
608        cache!(~buffer, 1, opts)?;
609
610        // Chomp indentation spaces, erroring on a tab
611        while isBlank!(~buffer)
612        {
613            cache!(~buffer, 1, opts)?;
614
615            if check!(~buffer => b'\t')
616            {
617                return Err(ScanError::InvalidTab);
618            }
619
620            if *can_borrow && *lines > 0
621            {
622                *can_borrow = false
623            }
624
625            advance!(*buffer, :stats, 1);
626        }
627
628        // Update detected indentation if required
629        if stats.column > indent
630        {
631            indent = stats.column;
632        }
633
634        // If its not a line break we're done, exit the loop
635        cache!(~buffer, 1, opts)?;
636        if !isBreak!(~buffer)
637        {
638            break;
639        }
640
641        // Otherwise eat the line and repeat
642        advance!(*buffer, :stats, @line);
643        *lines += 1;
644    }
645
646    // Note that we must set the lower bound of the indentation
647    // level, in case the YAML is invalid
648    if indent < base_indent + 1
649    {
650        indent = base_indent + 1
651    }
652
653    Ok(indent)
654}
655
656/// Skip any blanks (and .comments) until we reach a line
657/// ending or non blank character
658fn skip_blanks(opts: Flags, buffer: &mut &str, stats: &mut MStats, comments: bool) -> Result<()>
659{
660    cache!(~buffer, 1, opts)?;
661
662    while isBlank!(~buffer)
663    {
664        cache!(~buffer, 1, opts)?;
665        advance!(*buffer, :stats, 1);
666    }
667
668    if comments && check!(~buffer => b'#')
669    {
670        while !isBreakZ!(~buffer)
671        {
672            cache!(~buffer, 1, opts)?;
673            advance!(*buffer, :stats, 1);
674        }
675    }
676
677    Ok(())
678}
679
680/// The type of chomping associated with a block scalar
681#[derive(Debug, Clone, Copy, PartialEq, Eq)]
682enum ChompStyle
683{
684    /// Clipping is the default behavior used if no explicit
685    /// chomping indicator is specified. In this case, the
686    /// final line break character is preserved in the
687    /// scalar’s content. However, any trailing empty lines
688    /// are excluded from the scalar’s content.
689    Clip,
690    /// Stripping is specified by the '-' chomping
691    /// indicator. In this case, the final line break and
692    /// any trailing empty lines are excluded from the
693    /// scalar’s content.
694    Strip,
695    /// Keeping is specified by the “+” chomping indicator.
696    /// In this case, the final line break and any trailing
697    /// empty lines are considered to be part of the
698    /// scalar’s content. These additional lines are not
699    /// subject to folding.
700    Keep,
701}
702
703impl Default for ChompStyle
704{
705    fn default() -> Self
706    {
707        Self::Clip
708    }
709}
710
711/// Packager for transporting args into scan_chomp without
712/// triggering clippy
713#[derive(Debug)]
714struct ChompParams
715{
716    pub style: ChompStyle,
717    pub start: usize,
718    pub end:   usize,
719    pub lines: usize,
720}
721
722impl ChompParams
723{
724    fn new(style: ChompStyle, start: usize, end: usize, lines: usize) -> Self
725    {
726        Self {
727            style,
728            start,
729            end,
730            lines,
731        }
732    }
733}
734
735#[derive(Debug, Clone)]
736pub(in crate::scanner) struct Deferred<'de>
737{
738    opts:   Flags,
739    slice:  &'de str,
740    stats:  MStats,
741    indent: usize,
742    fold:   bool,
743}
744
745impl<'de> Deferred<'de>
746{
747    pub fn new(opts: Flags, slice: &'de str, stats: MStats, indent: usize, fold: bool) -> Self
748    {
749        Self {
750            opts,
751            slice,
752            stats,
753            indent,
754            fold,
755        }
756    }
757
758    pub fn into_token(self) -> Result<Token<'de>>
759    {
760        let Deferred {
761            opts,
762            slice,
763            mut stats,
764            indent,
765            fold,
766        } = self;
767
768        scan_block_scalar_eager(opts, slice, &mut stats, indent, fold).map(|(t, _)| t)
769    }
770}
771
772/// Indentation level explicitly set in a block scalar's
773/// headers
774type IndentHeader = Option<NonZeroU8>;
775
776const COMMENTS: bool = true;
777const SPACE: u8 = b' ';
778const NEWLINE: u8 = b'\n';
779
780#[cfg(test)]
781mod tests
782{
783    use pretty_assertions::assert_eq;
784    use ScalarStyle::{Folded, Literal};
785
786    use super::*;
787    use crate::scanner::scalar::test_utils::{normalize, TestResult, TEST_FLAGS};
788
789    macro_rules! cxt {
790        (flow -> $level:expr) => {
791            {
792                let mut c = Context::new();
793
794                for _ in 0..$level {
795                    c.flow_increment().unwrap();
796                }
797
798                c
799            }
800        };
801        (block -> [ $($indent:expr),+ ]) => {
802            {
803                let mut c = Context::new();
804                $( cxt!(@blk &mut c, $indent) )+;
805
806                c
807            }
808        };
809        (@blk $cxt:expr, $indent:expr) => {
810            $cxt.indent_increment($indent, 0, true).unwrap()
811        }
812    }
813
814    /* === LITERAL STYLE === */
815
816    #[test]
817    fn literal_simple() -> TestResult
818    {
819        let data = "|\n  this is a simple block scalar";
820        let mut stats = MStats::new();
821        let cxt = cxt!(block -> [0]);
822        let expected = Token::Scalar(cow!("this is a simple block scalar"), Literal);
823
824        let (token, _amt) =
825            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
826
827        assert_eq!(token, expected);
828
829        Ok(())
830    }
831
832    #[test]
833    fn literal_clip() -> TestResult
834    {
835        let data = "|\n  trailing lines...\n \n\n";
836        let mut stats = MStats::new();
837        let cxt = cxt!(block -> [0]);
838        let expected = Token::Scalar(cow!("trailing lines...\n"), Literal);
839
840        let (token, _amt) =
841            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
842
843        assert_eq!(token, expected);
844
845        Ok(())
846    }
847
848    #[test]
849    fn literal_strip() -> TestResult
850    {
851        let data = "|-\n  trailing lines...\n \n\n";
852        let mut stats = MStats::new();
853        let cxt = cxt!(block -> [0]);
854        let expected = Token::Scalar(cow!("trailing lines..."), Literal);
855
856        let (token, _amt) =
857            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
858
859        assert_eq!(token, expected);
860
861        Ok(())
862    }
863
864    #[test]
865    fn literal_keep() -> TestResult
866    {
867        let data = "|+\n  trailing lines...\n \n\n";
868        let mut stats = MStats::new();
869        let cxt = cxt!(block -> [0]);
870        let expected = Token::Scalar(cow!("trailing lines...\n\n\n"), Literal);
871
872        let (token, _amt) =
873            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
874
875        assert_eq!(token, expected);
876
877        Ok(())
878    }
879
880    #[test]
881    fn literal_line_folding() -> TestResult
882    {
883        let data = "|
884  some folded
885  lines
886  here
887";
888        let mut stats = MStats::new();
889        let cxt = cxt!(block -> [0]);
890        let expected = Token::Scalar(cow!("some folded\nlines\nhere\n"), Literal);
891
892        let (token, _amt) =
893            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
894
895        assert_eq!(token, expected);
896
897        Ok(())
898    }
899
900    #[test]
901    fn literal_preceding_breaks() -> TestResult
902    {
903        let data = "|-
904
905
906  some folded
907  lines
908  here
909";
910        let mut stats = MStats::new();
911        let cxt = cxt!(block -> [0]);
912        let expected = Token::Scalar(cow!("\n\nsome folded\nlines\nhere"), Literal);
913
914        let (token, _amt) =
915            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
916
917        assert_eq!(token, expected);
918
919        Ok(())
920    }
921
922    #[test]
923    fn literal_trailing_breaks() -> TestResult
924    {
925        let data = "|+
926  some folded
927  lines
928  here
929
930
931";
932        let mut stats = MStats::new();
933        let cxt = cxt!(block -> [0]);
934        let expected = Token::Scalar(cow!("some folded\nlines\nhere\n\n\n"), Literal);
935
936        let (token, _amt) =
937            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
938
939        assert_eq!(token, expected);
940
941        Ok(())
942    }
943
944    #[test]
945    fn literal_trailing_chars() -> TestResult
946    {
947        let data = "|+
948  some folded
949  lines
950  here
951
952
953some.other.key: value";
954        let mut stats = MStats::new();
955        let cxt = cxt!(block -> [0]);
956        let expected = Token::Scalar(cow!("some folded\nlines\nhere\n\n\n"), Literal);
957
958        let (token, _amt) =
959            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
960
961        assert_eq!(token, expected);
962
963        Ok(())
964    }
965
966    #[test]
967    fn literal_interior_breaks() -> TestResult
968    {
969        let data = "|-
970  this
971
972  has
973
974  breaks
975";
976        let mut stats = MStats::new();
977        let cxt = cxt!(block -> [0]);
978        let expected = Token::Scalar(cow!("this\n\nhas\n\nbreaks"), Literal);
979
980        let (token, _amt) =
981            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
982
983        assert_eq!(token, expected);
984
985        Ok(())
986    }
987
988    #[test]
989    fn literal_comment() -> TestResult
990    {
991        let data = "| # a comment here.\n  simple block scalar";
992        let mut stats = MStats::new();
993        let cxt = cxt!(block -> [0]);
994        let expected = Token::Scalar(cow!("simple block scalar"), Literal);
995
996        let (token, _amt) =
997            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, LITERAL).and_then(normalize)?;
998
999        assert_eq!(token, expected);
1000
1001        Ok(())
1002    }
1003
1004    /* === FOLDED STYLE === */
1005
1006    #[test]
1007    fn folded_simple() -> TestResult
1008    {
1009        let data = ">\n  this is a simple block scalar";
1010        let mut stats = MStats::new();
1011        let cxt = cxt!(block -> [0]);
1012        let expected = Token::Scalar(cow!("this is a simple block scalar"), Folded);
1013
1014        let (token, _amt) =
1015            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1016
1017        assert_eq!(token, expected);
1018
1019        Ok(())
1020    }
1021
1022    #[test]
1023    fn folded_clip() -> TestResult
1024    {
1025        let data = ">\n  trailing lines...\n \n\n";
1026        let mut stats = MStats::new();
1027        let cxt = cxt!(block -> [0]);
1028        let expected = Token::Scalar(cow!("trailing lines...\n"), Folded);
1029
1030        let (token, _amt) =
1031            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1032
1033        assert_eq!(token, expected);
1034
1035        Ok(())
1036    }
1037
1038    #[test]
1039    fn folded_strip() -> TestResult
1040    {
1041        let data = ">-\n  trailing lines...\n \n\n";
1042        let mut stats = MStats::new();
1043        let cxt = cxt!(block -> [0]);
1044        let expected = Token::Scalar(cow!("trailing lines..."), Folded);
1045
1046        let (token, _amt) =
1047            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1048
1049        assert_eq!(token, expected);
1050
1051        Ok(())
1052    }
1053
1054    #[test]
1055    fn folded_keep() -> TestResult
1056    {
1057        let data = ">+\n  trailing lines...\n \n\n";
1058        let mut stats = MStats::new();
1059        let cxt = cxt!(block -> [0]);
1060        let expected = Token::Scalar(cow!("trailing lines...\n\n\n"), Folded);
1061
1062        let (token, _amt) =
1063            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1064
1065        assert_eq!(token, expected);
1066
1067        Ok(())
1068    }
1069
1070    #[test]
1071    fn folded_line_folding() -> TestResult
1072    {
1073        let data = ">
1074  some folded
1075  lines
1076  here
1077";
1078        let mut stats = MStats::new();
1079        let cxt = cxt!(block -> [0]);
1080        let expected = Token::Scalar(cow!("some folded lines here\n"), Folded);
1081
1082        let (token, _amt) =
1083            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1084
1085        assert_eq!(token, expected);
1086
1087        Ok(())
1088    }
1089
1090    #[test]
1091    fn folded_preceding_breaks() -> TestResult
1092    {
1093        let data = ">-
1094
1095
1096  some folded
1097  lines
1098  here
1099";
1100        let mut stats = MStats::new();
1101        let cxt = cxt!(block -> [0]);
1102        let expected = Token::Scalar(cow!("\n\nsome folded lines here"), Folded);
1103
1104        let (token, _amt) =
1105            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1106
1107        assert_eq!(token, expected);
1108
1109        Ok(())
1110    }
1111
1112    #[test]
1113    fn folded_trailing_breaks() -> TestResult
1114    {
1115        let data = ">+
1116  some folded
1117  lines
1118  here
1119
1120
1121";
1122        let mut stats = MStats::new();
1123        let cxt = cxt!(block -> [0]);
1124        let expected = Token::Scalar(cow!("some folded lines here\n\n\n"), Folded);
1125
1126        let (token, _amt) =
1127            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1128
1129        assert_eq!(token, expected);
1130
1131        Ok(())
1132    }
1133
1134    #[test]
1135    fn folded_trailing_chars() -> TestResult
1136    {
1137        let data = ">+
1138  some folded
1139  lines
1140  here
1141
1142
1143some.other.key: value";
1144        let mut stats = MStats::new();
1145        let cxt = cxt!(block -> [0]);
1146        let expected = Token::Scalar(cow!("some folded lines here\n\n\n"), Folded);
1147
1148        let (token, _amt) =
1149            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1150
1151        assert_eq!(token, expected);
1152
1153        Ok(())
1154    }
1155
1156    #[test]
1157    fn folded_interior_breaks() -> TestResult
1158    {
1159        let data = ">-
1160  this
1161
1162  has
1163
1164  breaks
1165";
1166        let mut stats = MStats::new();
1167        let cxt = cxt!(block -> [0]);
1168        let expected = Token::Scalar(cow!("this\nhas\nbreaks"), Folded);
1169
1170        let (token, _amt) =
1171            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1172
1173        assert_eq!(token, expected);
1174
1175        Ok(())
1176    }
1177
1178    #[test]
1179    fn folded_comment() -> TestResult
1180    {
1181        let data = "> # a comment here.\n  simple block scalar";
1182        let mut stats = MStats::new();
1183        let cxt = cxt!(block -> [0]);
1184        let expected = Token::Scalar(cow!("simple block scalar"), Folded);
1185
1186        let (token, _amt) =
1187            scan_block_scalar(TEST_FLAGS, data, &mut stats, &cxt, !LITERAL).and_then(normalize)?;
1188
1189        assert_eq!(token, expected);
1190
1191        Ok(())
1192    }
1193
1194    const LITERAL: bool = false;
1195}