Skip to main content

jomini/text/
tape.rs

1use crate::{Error, ErrorKind, Scalar};
2use crate::{
3    Utf8Encoding, Windows1252Encoding,
4    data::is_boundary,
5    text::{ObjectReader, Operator},
6};
7
8/// Represents a valid text value
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum TextToken<'a> {
11    /// Start of an array
12    Array {
13        /// The index of the `TextToken::End` for this array
14        end: usize,
15
16        /// If this array contains a `MixedContainer` token
17        mixed: bool,
18    },
19
20    /// Start of an object
21    ///
22    /// The value of a property typically immediately follows a key token. However,
23    /// this is not guaranteed so always check if the end has been reached before
24    /// trying to decode a value. There are two main situations where this is not
25    /// guaranteed:
26    ///
27    /// - A non-equal operator (eg: `a > b` will be parsed to 3 instead of 2 tokens)
28    /// - If an object switches to a mixed container that is both an array and object
29    Object {
30        /// Index of the `TextToken::End` that signifies this objects's termination
31        end: usize,
32
33        /// If this object contains a `MixedContainer` token
34        mixed: bool,
35    },
36
37    /// Denotes the start of where a homogenous object or array becomes
38    /// heterogenous.
39    MixedContainer,
40
41    /// Extracted unquoted scalar value
42    Unquoted(Scalar<'a>),
43
44    /// Extracted quoted scalar value
45    Quoted(Scalar<'a>),
46
47    /// A parameter scalar
48    ///
49    /// Only seen so far in EU4. From the patch notes:
50    ///
51    /// > Scripted triggers or effects now support conditional compilation on arguments provided to them.
52    /// > You can now check for if an argument is defined or not and make the script look entirely different based on that.
53    /// > Syntax is `[[var_name] code here ]` for if variable is defined
54    ///
55    /// ```ignore
56    /// generate_advisor = { [[scaled_skill] if = { } ] }
57    /// ```
58    Parameter(Scalar<'a>),
59
60    /// An undefined parameter, see Parameter variant for more info.
61    ///
62    /// Syntax for undefined variable:
63    ///
64    /// ```ignore
65    /// [[!var_name] code here ]
66    /// ```
67    UndefinedParameter(Scalar<'a>),
68
69    /// A present, but non-equal operator token
70    Operator(Operator),
71
72    /// Index of the start of this object
73    End(usize),
74
75    /// The header token of the subsequent scalar. For instance, given
76    ///
77    /// ```ignore
78    /// color = rgb { 100 200 50 }
79    /// ```
80    ///
81    /// `rgb` would be a the header followed by a 3 element array
82    Header(Scalar<'a>),
83}
84
85impl<'a> TextToken<'a> {
86    /// Returns the scalar if the token contains a scalar
87    ///
88    /// ```
89    /// use jomini::{Scalar, TextToken};
90    /// assert_eq!(TextToken::Unquoted(Scalar::new(b"abc")).as_scalar(), Some(Scalar::new(b"abc")));
91    /// assert_eq!(TextToken::Quoted(Scalar::new(b"abc")).as_scalar(), Some(Scalar::new(b"abc")));
92    /// assert_eq!(TextToken::Header(Scalar::new(b"rgb")).as_scalar(), Some(Scalar::new(b"rgb")));
93    /// assert_eq!((TextToken::Object { end: 2, mixed: false }).as_scalar(), None);
94    /// ```
95    pub fn as_scalar(&self) -> Option<Scalar<'a>> {
96        match self {
97            TextToken::Header(s)
98            | TextToken::Unquoted(s)
99            | TextToken::Quoted(s)
100            | TextToken::Parameter(s)
101            | TextToken::UndefinedParameter(s) => Some(*s),
102            _ => None,
103        }
104    }
105}
106
107/// Creates a parser that a writes to a text tape
108#[derive(Debug, Default)]
109pub struct TextTapeParser;
110
111impl TextTapeParser {
112    /// Create a text parser
113    pub fn new() -> Self {
114        TextTapeParser
115    }
116
117    /// Parse the text format and return the data tape
118    pub fn parse_slice(self, data: &[u8]) -> Result<TextTape<'_>, Error> {
119        let mut res = TextTape::default();
120        self.parse_slice_into_tape(data, &mut res)?;
121        Ok(res)
122    }
123
124    /// Parse the text format into the given tape.
125    pub fn parse_slice_into_tape<'a>(
126        self,
127        data: &'a [u8],
128        tape: &mut TextTape<'a>,
129    ) -> Result<(), Error> {
130        let token_tape = &mut tape.token_tape;
131        token_tape.clear();
132        token_tape.reserve(data.len() / 5);
133        let mut state = ParserState {
134            data,
135            original_length: data.len(),
136            token_tape,
137            utf8_bom: false,
138        };
139
140        state.parse()?;
141        tape.utf8_bom = state.utf8_bom;
142
143        Ok(())
144    }
145}
146
147struct ParserState<'a, 'b> {
148    data: &'a [u8],
149    original_length: usize,
150    token_tape: &'b mut Vec<TextToken<'a>>,
151    utf8_bom: bool,
152}
153
154/// Houses the tape of tokens that is extracted from plaintext data
155#[derive(Debug, Default)]
156pub struct TextTape<'a> {
157    token_tape: Vec<TextToken<'a>>,
158    utf8_bom: bool,
159}
160
161impl<'a> TextTape<'a> {
162    /// Creates a windows 1252 object reader from the parsed tape
163    pub fn windows1252_reader(&self) -> ObjectReader<'a, '_, Windows1252Encoding> {
164        ObjectReader::new(self, Windows1252Encoding::new())
165    }
166
167    /// Creates a utf-8 object reader from the parsed tape
168    pub fn utf8_reader(&self) -> ObjectReader<'a, '_, Utf8Encoding> {
169        ObjectReader::new(self, Utf8Encoding::new())
170    }
171}
172
173#[derive(Debug, PartialEq)]
174enum ParseState {
175    Key,
176    KeyValueSeparator,
177    ObjectValue,
178    ArrayValue,
179    ParseOpen,
180}
181
182/// I'm not smart enough to figure out the behavior of handling escape sequences when
183/// when scanning multi-bytes, so this fallback is for when I was to reset and
184/// process bytewise. It is much slower, but escaped strings should be rare enough
185/// that this shouldn't be an issue
186fn parse_quote_scalar_fallback(d: &[u8]) -> Result<(Scalar<'_>, &[u8]), Error> {
187    let mut pos = 1;
188    while pos < d.len() {
189        if d[pos] == b'\\' {
190            pos += 2;
191        } else if d[pos] == b'"' {
192            let scalar = Scalar::new(&d[1..pos]);
193            return Ok((scalar, &d[pos + 1..]));
194        } else {
195            pos += 1;
196        }
197    }
198
199    Err(Error::eof())
200}
201
202#[cfg(not(target_arch = "x86_64"))]
203fn parse_quote_scalar(d: &[u8]) -> Result<(Scalar<'_>, &[u8]), Error> {
204    use crate::util::{contains_zero_byte, repeat_byte};
205    let sd = &d[1..];
206    unsafe {
207        let start_ptr = sd.as_ptr();
208        let end_ptr = start_ptr.add(sd.len() / 8 * 8);
209
210        let mut ptr = start_ptr;
211        while ptr < end_ptr {
212            let acc = (ptr as *const u64).read_unaligned();
213            if contains_zero_byte(acc ^ repeat_byte(b'\\')) {
214                break;
215            } else if contains_zero_byte(acc ^ repeat_byte(b'"')) {
216                while *ptr != b'"' {
217                    ptr = ptr.offset(1);
218                }
219
220                let offset = sub(ptr, start_ptr);
221                let (scalar, rest) = sd.split_at(offset);
222                let s = Scalar::new(scalar);
223                return Ok((s, &rest[1..]));
224            }
225            ptr = ptr.offset(8);
226        }
227    }
228
229    parse_quote_scalar_fallback(d)
230}
231
232#[cfg(target_arch = "x86_64")]
233fn parse_quote_scalar(d: &[u8]) -> Result<(Scalar<'_>, &[u8]), Error> {
234    #[target_feature(enable = "sse2")]
235    unsafe fn inner(d: &[u8]) -> Result<(Scalar<'_>, &[u8]), Error> {
236        unsafe {
237            // This is a re-implementation of memchr for a few reasons:
238            //   - We maintain zero dependencies
239            //   - memchr is optimized for finding a needle in a large haystack so we don't need the
240            //   following performance improvements from memchr:
241            //     - avx2 (there's no perf difference between sse2 and avx2 for our input)
242            //     - aligned loads (we use unaligned)
243            //     - loop unrolling
244            use core::arch::x86_64::*;
245            let haystack = &d[1..];
246            let start_ptr = haystack.as_ptr();
247            let mut ptr = start_ptr;
248            let loop_size = std::mem::size_of::<__m128i>();
249            let end_ptr = start_ptr.add(haystack.len() / loop_size * loop_size);
250            let quote = _mm_set1_epi8(b'"' as i8);
251            let slash = _mm_set1_epi8(b'\\' as i8);
252
253            while ptr < end_ptr {
254                let reg = _mm_loadu_si128(ptr as *const __m128i);
255                let slash_found = _mm_cmpeq_epi8(slash, reg);
256                if _mm_movemask_epi8(slash_found) != 0 {
257                    break;
258                }
259
260                let quote_found = _mm_cmpeq_epi8(quote, reg);
261                let mask = _mm_movemask_epi8(quote_found);
262                if mask != 0 {
263                    let at = sub(ptr, start_ptr);
264                    let end_idx = at + (mask.trailing_zeros() as usize);
265                    let scalar = std::slice::from_raw_parts(start_ptr, end_idx);
266                    let scalar = Scalar::new(scalar);
267                    return Ok((scalar, &haystack[end_idx + 1..]));
268                }
269
270                ptr = ptr.add(loop_size);
271            }
272
273            parse_quote_scalar_fallback(d)
274        }
275    }
276
277    // from memchr: "SSE2 is avalbale on all x86_64 targets, so no CPU feature detection is necessary"
278    unsafe { inner(d) }
279}
280
281#[inline]
282fn split_at_scalar_fallback(d: &[u8]) -> (Scalar<'_>, &[u8]) {
283    let start_ptr = d.as_ptr();
284    let end_ptr = unsafe { start_ptr.add(d.len()) };
285
286    let nind = unsafe { forward_search(start_ptr, end_ptr, is_boundary) };
287    let mut ind = nind.unwrap_or(d.len());
288
289    // To work with cases where we have "==bar" we ensure that found index is at least one
290    ind = std::cmp::max(ind, 1);
291    let (scalar, rest) = d.split_at(ind);
292    (Scalar::new(scalar), rest)
293}
294
295#[cfg(not(target_arch = "x86_64"))]
296#[inline]
297fn split_at_scalar(d: &[u8]) -> (Scalar<'_>, &[u8]) {
298    split_at_scalar_fallback(d)
299}
300
301#[cfg(target_arch = "x86_64")]
302#[inline]
303fn split_at_scalar(d: &[u8]) -> (Scalar<'_>, &[u8]) {
304    #[target_feature(enable = "sse2")]
305    #[inline]
306    #[allow(overflowing_literals)]
307    unsafe fn inner(d: &[u8]) -> (Scalar<'_>, &[u8]) {
308        unsafe {
309            use core::arch::x86_64::*;
310            let start_ptr = d.as_ptr();
311            let loop_size = std::mem::size_of::<__m128i>();
312            let end_ptr = d.as_ptr_range().end.sub(loop_size.min(d.len()));
313            let mut ptr = start_ptr;
314
315            // Here we use SIMD instructions to detect certain bytes.
316            // The method used is described here:
317            // http://0x80.pl/articles/simd-byte-lookup.html
318            //
319            // Loop partially auto-generated by the author's python code:
320            // https://github.com/WojciechMula/simd-byte-lookup
321            //
322            // Interestingly the naive approach had the best performance.
323            // To save myself some future time, here is the nibble diagram
324            // of the characters that define a boundary character
325            //
326            //    lo / hi nibble
327            //    +----------------- | ---------------
328            //    | 0 1 2 3 4 5 6 7  | 8 9 a b c d e f
329            //  --+----------------- | ---------------
330            //  0 | . . x . . . . .  | . . . . . . . .
331            //  1 | . . x . . . . .  | . . . . . . . .
332            //  2 | . . . . . . . .  | . . . . . . . .
333            //  3 | . . x . . . . .  | . . . . . . . .
334            //  4 | . . . . . . . .  | . . . . . . . .
335            //  5 | . . . . . . . .  | . . . . . . . .
336            //  6 | . . . . . . . .  | . . . . . . . .
337            //  7 | . . . . . . . .  | . . . . . . . .
338            //  8 | . . . . . . . .  | . . . . . . . .
339            //  9 | x . . . . . . .  | . . . . . . . .
340            //  a | x . . . . . . .  | . . . . . . . .
341            //  b | x . . . . x . x  | . . . . . . . .
342            //  c | x . . x . . . .  | . . . . . . . .
343            //  d | x . . x . x . x  | . . . . . . . .
344            //  e | . . . x . . . .  | . . . . . . . .
345            //  f | . . . . . . . .  | . . . . . . . .
346            //
347            // \t = 0x09
348            // \n = 0x0a
349            // \v = 0x0b *
350            // \f = 0x0c *
351            // \r = 0x0d
352            // sp = 0x20
353            //  ! = 0x21 *
354            //  # = 0x23
355            //  ; = 0x3b
356            //  < = 0x3c
357            //  = = 0x3d
358            //  > = 0x3e
359            //  [ = 0x5b
360            //  ] = 0x5d
361            //  { = 0x7b
362            //  } = 0x7d
363            // * = unknown if boundary character. Can be removed for perf
364            while ptr < end_ptr {
365                let input = _mm_loadu_si128(ptr as *const __m128i);
366                let t0 = _mm_cmpeq_epi8(input, _mm_set1_epi8(9));
367                let mut result = t0;
368                let t1 = _mm_cmpeq_epi8(input, _mm_set1_epi8(10));
369                result = _mm_or_si128(result, t1);
370                let t2 = _mm_cmpeq_epi8(input, _mm_set1_epi8(13));
371                result = _mm_or_si128(result, t2);
372                let t3 = _mm_cmpeq_epi8(input, _mm_set1_epi8(32));
373                result = _mm_or_si128(result, t3);
374                let t4 = _mm_cmpeq_epi8(input, _mm_set1_epi8(35));
375                result = _mm_or_si128(result, t4);
376                let t5 = _mm_cmpeq_epi8(input, _mm_set1_epi8(59));
377                result = _mm_or_si128(result, t5);
378                let t6 = _mm_cmpeq_epi8(input, _mm_set1_epi8(60));
379                result = _mm_or_si128(result, t6);
380                let t7 = _mm_cmpeq_epi8(input, _mm_set1_epi8(61));
381                result = _mm_or_si128(result, t7);
382                let t8 = _mm_cmpeq_epi8(input, _mm_set1_epi8(62));
383                result = _mm_or_si128(result, t8);
384                let t9 = _mm_cmpeq_epi8(input, _mm_set1_epi8(123));
385                result = _mm_or_si128(result, t9);
386                let t10 = _mm_cmpeq_epi8(input, _mm_set1_epi8(125));
387                result = _mm_or_si128(result, t10);
388                let t11 = _mm_cmpeq_epi8(input, _mm_set1_epi8(91));
389                result = _mm_or_si128(result, t11);
390                let t12 = _mm_cmpeq_epi8(input, _mm_set1_epi8(93));
391                result = _mm_or_si128(result, t12);
392
393                let found_mask = _mm_movemask_epi8(result);
394                if found_mask != 0 {
395                    let at = sub(ptr, start_ptr);
396                    let end_idx = at + (found_mask.trailing_zeros() as usize);
397                    let end_idx = std::cmp::max(end_idx, 1);
398                    let scalar = std::slice::from_raw_parts(start_ptr, end_idx);
399                    let scalar = Scalar::new(scalar);
400                    return (scalar, &d[end_idx..]);
401                }
402                ptr = ptr.add(loop_size);
403            }
404
405            split_at_scalar_fallback(d)
406        }
407    }
408
409    // from memchr: "SSE2 is avalbale on all x86_64 targets, so no CPU feature detection is necessary"
410    unsafe { inner(d) }
411}
412
413impl<'a> TextTape<'a> {
414    /// Creates a new text tape
415    pub fn new() -> Self {
416        Default::default()
417    }
418
419    /// Convenience method for creating a text parser and parsing the given input
420    pub fn from_slice(data: &[u8]) -> Result<TextTape<'_>, Error> {
421        TextTapeParser.parse_slice(data)
422    }
423
424    /// Returns a parser for text data
425    pub fn parser() -> TextTapeParser {
426        TextTapeParser
427    }
428
429    /// Return the parsed tokens
430    pub fn tokens(&self) -> &[TextToken<'a>] {
431        self.token_tape.as_slice()
432    }
433
434    /// Return a mutable reference to the tokens for in-place modification
435    ///
436    /// # Safety and Correctness Caveats
437    ///
438    /// When modifying tokens in-place, the caller is **fully responsible** for ensuring
439    /// that the resulting token stream maintains structural validity. Improper modifications
440    /// can lead to undefined behavior, panics, or incorrect parsing results.
441    ///
442    /// ## Critical Requirements:
443    ///
444    /// - `Object { end: usize }` and `Array { end: usize }` tokens must have valid `end` indices
445    /// - The `end` index must point to the corresponding `End(start)` token
446    /// - `End(start)` tokens must have the correct `start` index pointing back to the opening token
447    /// - Modifying container tokens requires updating **both** the opening and closing indices
448    /// - Object keys must be scalar tokens
449    ///
450    /// ## Safe Modifications
451    /// The following modifications are generally safe:
452    /// - Changing scalar values (`Unquoted` ↔ `Quoted`)
453    /// - Modifying operator types (`Operator::Exists` → `Operator::Equal`)
454    /// - Updating scalar content (with same lifetime constraints)
455    ///
456    /// ## Example: Safe Operator Modification
457    /// ```rust
458    /// # use jomini::{TextTape, text::{TextToken, Operator}};
459    /// let mut tape = TextTape::from_slice(b"foo ?= 10").unwrap();
460    ///
461    /// // Safe: Replace exists operator with equals operator
462    /// for token in tape.tokens_mut() {
463    ///     if let TextToken::Operator(Operator::Exists) = token {
464    ///         *token = TextToken::Operator(Operator::Equal);
465    ///     }
466    /// }
467    /// ```
468    pub fn tokens_mut(&mut self) -> &mut [TextToken<'a>] {
469        self.token_tape.as_mut_slice()
470    }
471
472    /// Return if there was a UTF8 BOM in the data
473    pub fn utf8_bom(&self) -> bool {
474        self.utf8_bom
475    }
476}
477
478impl<'a> ParserState<'a, '_> {
479    fn offset(&self, data: &[u8]) -> usize {
480        self.original_length - data.len()
481    }
482
483    /// Skips whitespace that may terminate the file
484    #[inline]
485    fn skip_ws_t(&self, data: &'a [u8]) -> Option<&'a [u8]> {
486        unsafe {
487            let start_ptr = data.as_ptr_range().start;
488            let end_ptr = data.as_ptr_range().end;
489            let mut ptr = start_ptr;
490            while ptr < end_ptr {
491                match *ptr {
492                    b' ' | b'\t' | b'\n' | b'\r' | b';' => {}
493                    b'#' => loop {
494                        ptr = ptr.add(1);
495                        if ptr == end_ptr {
496                            return None;
497                        } else if *ptr == b'\n' {
498                            break;
499                        }
500                    },
501                    _ => {
502                        let rest = std::slice::from_raw_parts(ptr, sub(end_ptr, ptr));
503                        return Some(rest);
504                    }
505                }
506                ptr = ptr.add(1);
507            }
508        }
509
510        None
511    }
512
513    #[inline]
514    fn parse_quote_scalar(&mut self, d: &'a [u8]) -> Result<&'a [u8], Error> {
515        let (scalar, rest) = parse_quote_scalar(d)?;
516        self.token_tape.push(TextToken::Quoted(scalar));
517        Ok(rest)
518    }
519
520    #[inline(never)]
521    fn parse_variable(&mut self, d: &'a [u8]) -> Result<&'a [u8], Error> {
522        // detect if the variable is interpolated
523        if d.get(1).is_some_and(|&x| x == b'[') {
524            let mut pos = 2;
525            while pos < d.len() {
526                if d[pos] == b']' {
527                    let (scalar, rest) = d.split_at(pos + 1);
528                    let scalar = Scalar::new(scalar);
529                    self.token_tape.push(TextToken::Unquoted(scalar));
530                    return Ok(rest);
531                } else {
532                    pos += 1;
533                }
534            }
535
536            Err(Error::eof())
537        } else {
538            let (scalar, rest) = split_at_scalar(d);
539            self.token_tape.push(TextToken::Unquoted(scalar));
540            Ok(rest)
541        }
542    }
543
544    #[inline]
545    fn parse_scalar(&mut self, d: &'a [u8]) -> &'a [u8] {
546        let (scalar, rest) = split_at_scalar(d);
547        self.token_tape.push(TextToken::Unquoted(scalar));
548        rest
549    }
550
551    /// Clear previously parsed data and parse the given data
552    #[inline]
553    pub fn parse(&mut self) -> Result<(), Error> {
554        let mut data = self.data;
555        let mut state = ParseState::Key;
556
557        self.utf8_bom = data.get(..3).is_some_and(|x| x == [0xef, 0xbb, 0xbf]);
558        if self.utf8_bom {
559            data = &data[3..];
560        }
561
562        let mut mixed_mode = false;
563        let mut parent_ind = 0;
564        loop {
565            let d = match self.skip_ws_t(data) {
566                Some(d) => d,
567                None => {
568                    if state != ParseState::Key {
569                        return Err(Error::eof());
570                    }
571
572                    if parent_ind == 0 {
573                        return Ok(());
574                    } else {
575                        // Support for files that don't have enough closing brackets (ugh)
576                        let grand_ind = match self.token_tape.get(parent_ind) {
577                            Some(TextToken::Array { end, .. }) => *end,
578                            Some(TextToken::Object { end, .. }) => *end,
579                            _ => 0,
580                        };
581
582                        if grand_ind == 0 {
583                            let end = self.token_tape.len();
584                            self.token_tape.push(TextToken::End(parent_ind));
585                            self.token_tape[parent_ind] = TextToken::Object { end, mixed: false };
586                            return Ok(());
587                        } else {
588                            return Err(Error::eof());
589                        }
590                    }
591                }
592            };
593
594            data = d;
595            match state {
596                ParseState::Key => {
597                    match data[0] {
598                        b'}' | b']' => {
599                            let saved_mixed = mixed_mode;
600                            let grand_ind = match self.token_tape.get(parent_ind) {
601                                Some(TextToken::Array { end, .. }) => *end,
602                                Some(TextToken::Object { end, .. }) => *end,
603                                _ => 0,
604                            };
605
606                            match self.token_tape.get(grand_ind) {
607                                Some(TextToken::Array { mixed, .. }) => {
608                                    mixed_mode = *mixed;
609                                    state = ParseState::ArrayValue;
610                                }
611                                Some(TextToken::Object { mixed, .. }) => {
612                                    mixed_mode = *mixed;
613                                    state = if mixed_mode {
614                                        ParseState::ArrayValue
615                                    } else {
616                                        ParseState::Key
617                                    }
618                                }
619                                _ => {
620                                    mixed_mode = false;
621                                    state = ParseState::Key;
622                                }
623                            };
624
625                            let end_idx = self.token_tape.len();
626                            if parent_ind == 0 && grand_ind == 0 {
627                                // Allow extraneous close braces to support malformatted game files (ugh)
628                                data = &data[1..];
629                                continue;
630                            }
631
632                            self.token_tape.push(TextToken::End(parent_ind));
633                            self.token_tape[parent_ind] = TextToken::Object {
634                                end: end_idx,
635                                mixed: saved_mixed,
636                            };
637                            parent_ind = grand_ind;
638                            data = &data[1..];
639                        }
640
641                        // Empty object or token header
642                        b'{' => {
643                            data = self.skip_ws_t(&data[1..]).ok_or_else(Error::eof)?;
644                            if data[0] == b'}' {
645                                data = &data[1..];
646                                continue;
647                            }
648
649                            if let Some(last) = self.token_tape.last_mut()
650                                && let TextToken::Unquoted(header) = last
651                            {
652                                *last = TextToken::Header(*header);
653                                self.token_tape.push(TextToken::Array {
654                                    end: 0,
655                                    mixed: false,
656                                });
657                                state = ParseState::ParseOpen;
658                                continue;
659                            }
660
661                            return Err(Error::new(ErrorKind::InvalidSyntax {
662                                offset: self.offset(data),
663                                msg: String::from("invalid syntax for token headers"),
664                            }));
665                        }
666
667                        b'[' => {
668                            data = self.parse_parameter_definition(
669                                data,
670                                &mut parent_ind,
671                                &mut state,
672                                false,
673                            )?;
674                        }
675
676                        b'"' => {
677                            data = self.parse_quote_scalar(data)?;
678                            state = ParseState::KeyValueSeparator;
679                        }
680
681                        b'@' => {
682                            data = self.parse_variable(data)?;
683                            state = ParseState::KeyValueSeparator;
684                        }
685
686                        _ => {
687                            data = self.parse_scalar(data);
688                            state = ParseState::KeyValueSeparator;
689                        }
690                    }
691                }
692                ParseState::KeyValueSeparator => match data {
693                    [b'<', b'=', ..] => {
694                        self.token_tape
695                            .push(TextToken::Operator(Operator::LessThanEqual));
696                        data = &data[2..];
697                        state = ParseState::ObjectValue;
698                    }
699                    [b'<', ..] => {
700                        self.token_tape
701                            .push(TextToken::Operator(Operator::LessThan));
702                        data = &data[1..];
703                        state = ParseState::ObjectValue;
704                    }
705                    [b'>', b'=', ..] => {
706                        self.token_tape
707                            .push(TextToken::Operator(Operator::GreaterThanEqual));
708                        data = &data[2..];
709                        state = ParseState::ObjectValue;
710                    }
711                    [b'>', ..] => {
712                        self.token_tape
713                            .push(TextToken::Operator(Operator::GreaterThan));
714                        data = &data[1..];
715                        state = ParseState::ObjectValue;
716                    }
717                    [b'!', b'=', ..] => {
718                        self.token_tape
719                            .push(TextToken::Operator(Operator::NotEqual));
720                        data = &data[2..];
721                        state = ParseState::ObjectValue;
722                    }
723                    [b'?', b'=', ..] => {
724                        self.token_tape.push(TextToken::Operator(Operator::Exists));
725                        data = &data[2..];
726                        state = ParseState::ObjectValue;
727                    }
728                    [b'=', b'=', ..] => {
729                        self.token_tape.push(TextToken::Operator(Operator::Exact));
730                        data = &data[2..];
731                        state = ParseState::ObjectValue;
732                    }
733                    [b'=', ..] if mixed_mode => {
734                        self.token_tape.push(TextToken::Operator(Operator::Equal));
735                        data = &data[1..];
736                    }
737                    [b'=', ..] => {
738                        data = &data[1..];
739                        state = ParseState::ObjectValue;
740                    }
741                    [b'{', ..] => {
742                        state = ParseState::ObjectValue;
743                    }
744                    [b'}', ..] => {
745                        self.token_tape
746                            .insert(self.token_tape.len() - 1, TextToken::MixedContainer);
747                        state = ParseState::ArrayValue;
748                        mixed_mode = true;
749                    }
750                    _ => {
751                        self.token_tape
752                            .insert(self.token_tape.len() - 1, TextToken::MixedContainer);
753                        state = ParseState::ArrayValue;
754                        mixed_mode = true;
755                    }
756                },
757                ParseState::ObjectValue => match data[0] {
758                    b'{' => {
759                        self.token_tape.push(TextToken::Array {
760                            end: 0,
761                            mixed: false,
762                        });
763                        state = ParseState::ParseOpen;
764                        data = &data[1..];
765                    }
766
767                    b'}' => {
768                        return Err(Error::new(ErrorKind::InvalidSyntax {
769                            msg: String::from("encountered '}' for object value"),
770                            offset: self.offset(data),
771                        }));
772                    }
773
774                    b'"' => {
775                        data = self.parse_quote_scalar(data)?;
776                        state = ParseState::Key;
777                    }
778                    b'@' => {
779                        data = self.parse_variable(data)?;
780                        state = ParseState::Key;
781                    }
782                    _ => {
783                        data = self.parse_scalar(data);
784                        state = ParseState::Key;
785                    }
786                },
787                ParseState::ParseOpen => {
788                    match data[0] {
789                        // Empty array
790                        b'}' => {
791                            let ind = self.token_tape.len() - 1;
792
793                            match self.token_tape.get(parent_ind) {
794                                Some(TextToken::Array { mixed, .. }) => {
795                                    mixed_mode = *mixed;
796                                    state = ParseState::ArrayValue;
797                                }
798                                Some(TextToken::Object { mixed, .. }) => {
799                                    mixed_mode = *mixed;
800                                    state = if mixed_mode {
801                                        ParseState::ArrayValue
802                                    } else {
803                                        ParseState::Key
804                                    }
805                                }
806                                _ => {
807                                    mixed_mode = false;
808                                    state = ParseState::Key;
809                                }
810                            };
811
812                            self.token_tape[ind] = TextToken::Array {
813                                end: ind + 1,
814                                mixed: false,
815                            };
816                            self.token_tape.push(TextToken::End(ind));
817                            data = &data[1..];
818                            continue;
819                        }
820
821                        // start of a parameter definition
822                        b'[' => {
823                            if mixed_mode {
824                                return Err(Error::new(ErrorKind::InvalidSyntax {
825                                    msg: String::from(
826                                        "mixed object and array container not expected",
827                                    ),
828                                    offset: self.offset(data),
829                                }));
830                            }
831
832                            data = self.parse_parameter_definition(
833                                data,
834                                &mut parent_ind,
835                                &mut state,
836                                true,
837                            )?;
838                            continue;
839                        }
840
841                        // array of objects, another array
842                        b'{' => {
843                            let scratch = self.skip_ws_t(&data[1..]).ok_or_else(Error::eof)?;
844                            if scratch[0] == b'}' {
845                                data = &scratch[1..];
846                                continue;
847                            }
848
849                            mixed_mode = false;
850                            let ind = self.token_tape.len() - 1;
851                            self.token_tape[ind] = TextToken::Array {
852                                end: parent_ind,
853                                mixed: mixed_mode,
854                            };
855                            parent_ind = ind;
856                            state = ParseState::ArrayValue;
857                            continue;
858                        }
859                        b'"' => {
860                            data = self.parse_quote_scalar(data)?;
861                        }
862                        b'@' => {
863                            data = self.parse_variable(data)?;
864                        }
865                        _ => {
866                            data = self.parse_scalar(data);
867                        }
868                    }
869
870                    if mixed_mode
871                        && let Some(
872                            TextToken::Array { mixed, .. } | TextToken::Object { mixed, .. },
873                        ) = self.token_tape.get_mut(parent_ind)
874                    {
875                        *mixed = mixed_mode;
876                    }
877
878                    mixed_mode = false;
879                    data = self.skip_ws_t(data).ok_or_else(Error::eof)?;
880                    match data[0] {
881                        b'=' | b'>' | b'<' | b'?' | b'!' => {
882                            let ind = self.token_tape.len() - 2;
883                            self.token_tape[ind] = TextToken::Object {
884                                end: parent_ind,
885                                mixed: mixed_mode,
886                            };
887                            parent_ind = ind;
888                            state = ParseState::KeyValueSeparator;
889                        }
890                        _ => {
891                            let ind = self.token_tape.len() - 2;
892                            self.token_tape[ind] = TextToken::Array {
893                                end: parent_ind,
894                                mixed: mixed_mode,
895                            };
896                            parent_ind = ind;
897                            state = ParseState::ArrayValue;
898                        }
899                    }
900                }
901                ParseState::ArrayValue => match data[0] {
902                    b'{' => {
903                        self.token_tape.push(TextToken::Array {
904                            end: 0,
905                            mixed: false,
906                        });
907                        state = ParseState::ParseOpen;
908                        data = &data[1..];
909                    }
910                    b'}' => {
911                        let saved_mixed_mode = mixed_mode;
912                        let (grand_ind, is_array) = match self.token_tape.get(parent_ind) {
913                            Some(TextToken::Array { end, .. }) => (*end, true),
914                            Some(TextToken::Object { end, .. }) => (*end, false),
915                            _ => (0, false),
916                        };
917
918                        match self.token_tape.get(grand_ind) {
919                            Some(TextToken::Array { mixed, .. }) => {
920                                mixed_mode = *mixed;
921                                state = ParseState::ArrayValue;
922                            }
923                            Some(TextToken::Object { mixed, .. }) => {
924                                mixed_mode = *mixed;
925                                state = if mixed_mode {
926                                    ParseState::ArrayValue
927                                } else {
928                                    ParseState::Key
929                                }
930                            }
931                            _ => {
932                                mixed_mode = false;
933                                state = ParseState::Key;
934                            }
935                        };
936
937                        if parent_ind == 0 && grand_ind == 0 {
938                            return Err(Error::new(ErrorKind::StackEmpty {
939                                offset: self.offset(data),
940                            }));
941                        }
942
943                        let end_idx = self.token_tape.len();
944                        self.token_tape[parent_ind] = if is_array {
945                            TextToken::Array {
946                                end: end_idx,
947                                mixed: saved_mixed_mode,
948                            }
949                        } else {
950                            TextToken::Object {
951                                end: end_idx,
952                                mixed: saved_mixed_mode,
953                            }
954                        };
955
956                        self.token_tape.push(TextToken::End(parent_ind));
957                        parent_ind = grand_ind;
958                        data = &data[1..];
959                    }
960                    b'"' => {
961                        data = self.parse_quote_scalar(data)?;
962                        state = ParseState::ArrayValue;
963                    }
964                    b'@' => {
965                        data = self.parse_variable(data)?;
966                        state = ParseState::ArrayValue;
967                    }
968                    b'<' | b'>' | b'!' | b'=' => {
969                        if !mixed_mode {
970                            let can_precede_operator = match self.token_tape.last() {
971                                // Regular scalars can precede operators
972                                Some(token) if token.as_scalar().is_some() => true,
973                                // End tokens that close objects or arrays in object template syntax
974                                // For object template syntax, we should just continue parsing without creating operators
975                                Some(TextToken::End(start_idx)) => {
976                                    if matches!(
977                                        self.token_tape.get(*start_idx),
978                                        Some(TextToken::Object { .. })
979                                            | Some(TextToken::Array { .. })
980                                    ) {
981                                        // This is object template syntax, don't treat as operator, continue parsing value
982                                        if data[0] == b'=' {
983                                            data = &data[1..];
984                                            continue;
985                                        }
986                                    }
987                                    false
988                                }
989                                _ => false,
990                            };
991
992                            if can_precede_operator {
993                                self.token_tape
994                                    .insert(self.token_tape.len() - 1, TextToken::MixedContainer);
995                                mixed_mode = true;
996                            } else {
997                                return Err(Error::new(ErrorKind::InvalidSyntax {
998                                    msg: String::from("expected a scalar to precede an operator"),
999                                    offset: self.offset(data) - 1,
1000                                }));
1001                            }
1002                        }
1003
1004                        match data {
1005                            [b'<', b'=', ..] => {
1006                                self.token_tape
1007                                    .push(TextToken::Operator(Operator::LessThanEqual));
1008                                data = &data[2..];
1009                            }
1010                            [b'<', ..] => {
1011                                self.token_tape
1012                                    .push(TextToken::Operator(Operator::LessThan));
1013                                data = &data[1..];
1014                            }
1015                            [b'>', b'=', ..] => {
1016                                self.token_tape
1017                                    .push(TextToken::Operator(Operator::GreaterThanEqual));
1018                                data = &data[2..];
1019                            }
1020                            [b'>', ..] => {
1021                                self.token_tape
1022                                    .push(TextToken::Operator(Operator::GreaterThan));
1023                                data = &data[1..];
1024                            }
1025                            [b'!', b'=', ..] => {
1026                                self.token_tape
1027                                    .push(TextToken::Operator(Operator::NotEqual));
1028                                data = &data[2..];
1029                            }
1030                            [b'=', b'=', ..] => {
1031                                self.token_tape.push(TextToken::Operator(Operator::Exact));
1032                                data = &data[2..];
1033                            }
1034                            [b'=', ..] => {
1035                                self.token_tape.push(TextToken::Operator(Operator::Equal));
1036                                data = &data[1..];
1037                            }
1038                            _ => {
1039                                return Err(Error::new(ErrorKind::InvalidSyntax {
1040                                    msg: String::from("unrecognized operator"),
1041                                    offset: self.offset(data) - 1,
1042                                }));
1043                            }
1044                        }
1045                    }
1046                    _ => {
1047                        data = self.parse_scalar(data);
1048                        state = ParseState::ArrayValue;
1049                    }
1050                },
1051            }
1052        }
1053    }
1054
1055    fn parse_parameter_definition(
1056        &mut self,
1057        data: &'a [u8],
1058        parent_ind: &mut usize,
1059        state: &mut ParseState,
1060        initial: bool,
1061    ) -> Result<&'a [u8], Error> {
1062        if !matches!(data.get(1), Some(&x) if x == b'[') {
1063            return Err(Error::new(ErrorKind::InvalidSyntax {
1064                offset: self.offset(data),
1065                msg: String::from("expected start of parameter definition"),
1066            }));
1067        }
1068
1069        // This is for parse_open to know and we signal that a parameter
1070        // definition means an object as parameters should be uniquely named
1071        if initial {
1072            let ind = self.token_tape.len() - 1;
1073            self.token_tape[ind] = TextToken::Object {
1074                end: *parent_ind,
1075                mixed: false,
1076            };
1077            *parent_ind = ind;
1078        }
1079
1080        let is_undefined = matches!(data.get(2), Some(&x) if x == b'!');
1081        let data = data
1082            .get(2 + is_undefined as usize..)
1083            .ok_or_else(Error::eof)?;
1084
1085        if data.is_empty() {
1086            return Err(Error::eof());
1087        }
1088
1089        let (scalar, data) = split_at_scalar(data);
1090        if !matches!(data.first(), Some(&x) if x == b']') {
1091            return Err(Error::new(ErrorKind::InvalidSyntax {
1092                offset: self.offset(data),
1093                msg: String::from("expected end of parameter name"),
1094            }));
1095        }
1096
1097        let data = &data[1..];
1098
1099        let token = if is_undefined {
1100            TextToken::UndefinedParameter(scalar)
1101        } else {
1102            TextToken::Parameter(scalar)
1103        };
1104
1105        self.token_tape.push(token);
1106
1107        // now we have to determine if are looking at a parameter value or
1108        // parameter object. We know when we are looking at a parameter value
1109        // when `]` is encountered first after the value else it's a key
1110        let data = self.skip_ws_t(data).ok_or_else(Error::eof)?;
1111        let (key_or_value, data) = split_at_scalar(data);
1112        let data = self.skip_ws_t(data).ok_or_else(Error::eof)?;
1113        if data[0] == b']' {
1114            self.token_tape.push(TextToken::Unquoted(key_or_value));
1115            *state = ParseState::Key;
1116            Ok(&data[1..])
1117        } else {
1118            let grand_ind = *parent_ind;
1119            *parent_ind = self.token_tape.len();
1120            self.token_tape.push(TextToken::Object {
1121                end: grand_ind,
1122                mixed: false,
1123            });
1124            self.token_tape.push(TextToken::Unquoted(key_or_value));
1125            *state = ParseState::KeyValueSeparator;
1126            Ok(data)
1127        }
1128    }
1129}
1130
1131fn sub(a: *const u8, b: *const u8) -> usize {
1132    debug_assert!(a >= b);
1133    (a as usize) - (b as usize)
1134}
1135
1136#[inline(always)]
1137unsafe fn forward_search<F: Fn(u8) -> bool>(
1138    start_ptr: *const u8,
1139    end_ptr: *const u8,
1140    confirm: F,
1141) -> Option<usize> {
1142    unsafe {
1143        let mut ptr = start_ptr;
1144        while ptr < end_ptr {
1145            if confirm(*ptr) {
1146                return Some(sub(ptr, start_ptr));
1147            }
1148            ptr = ptr.offset(1);
1149        }
1150
1151        None
1152    }
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157    use super::*;
1158
1159    fn parse(data: &[u8]) -> Result<TextTape<'_>, Error> {
1160        TextTape::from_slice(data)
1161    }
1162
1163    #[test]
1164    fn test_size_of_text_token() {
1165        let token_size = std::mem::size_of::<TextToken>();
1166        assert!(token_size <= 24);
1167        assert_eq!(
1168            token_size,
1169            std::mem::size_of::<Scalar>() + std::mem::size_of::<usize>()
1170        );
1171    }
1172
1173    #[test]
1174    fn test_binary_tokens_dont_need_to_be_dropped() {
1175        assert!(!std::mem::needs_drop::<TextToken>());
1176    }
1177
1178    #[test]
1179    fn test_simple_event() {
1180        let data = b"foo=bar";
1181
1182        assert_eq!(
1183            parse(&data[..]).unwrap().token_tape,
1184            vec![
1185                TextToken::Unquoted(Scalar::new(b"foo")),
1186                TextToken::Unquoted(Scalar::new(b"bar")),
1187            ]
1188        );
1189    }
1190
1191    #[test]
1192    fn test_simple_event_with_spaces() {
1193        let data = b"  \t\t foo =bar \r\ndef=\tqux";
1194
1195        assert_eq!(
1196            parse(&data[..]).unwrap().token_tape,
1197            vec![
1198                TextToken::Unquoted(Scalar::new(b"foo")),
1199                TextToken::Unquoted(Scalar::new(b"bar")),
1200                TextToken::Unquoted(Scalar::new(b"def")),
1201                TextToken::Unquoted(Scalar::new(b"qux")),
1202            ]
1203        );
1204    }
1205
1206    #[test]
1207    fn test_scalars_with_quotes() {
1208        let data = br#""foo"="bar" "3"="1444.11.11""#;
1209        assert_eq!(
1210            parse(&data[..]).unwrap().token_tape,
1211            vec![
1212                TextToken::Quoted(Scalar::new(b"foo")),
1213                TextToken::Quoted(Scalar::new(b"bar")),
1214                TextToken::Quoted(Scalar::new(b"3")),
1215                TextToken::Quoted(Scalar::new(b"1444.11.11")),
1216            ]
1217        );
1218    }
1219
1220    #[test]
1221    fn test_scalars_with_quoteas_delimiters() {
1222        let data = br#""foo"="bar"3="1444.11.11""#;
1223        assert_eq!(
1224            parse(&data[..]).unwrap().token_tape,
1225            vec![
1226                TextToken::Quoted(Scalar::new(b"foo")),
1227                TextToken::Quoted(Scalar::new(b"bar")),
1228                TextToken::Unquoted(Scalar::new(b"3")),
1229                TextToken::Quoted(Scalar::new(b"1444.11.11")),
1230            ]
1231        );
1232    }
1233
1234    #[test]
1235    fn test_escaped_quotes() {
1236        let data = br#"name = "Joe \"Captain\" Rogers""#;
1237
1238        assert_eq!(
1239            parse(&data[..]).unwrap().token_tape,
1240            vec![
1241                TextToken::Unquoted(Scalar::new(b"name")),
1242                TextToken::Quoted(Scalar::new(br#"Joe \"Captain\" Rogers"#)),
1243            ]
1244        );
1245    }
1246
1247    #[test]
1248    fn test_escaped_quotes_short() {
1249        let data = br#"name = "J Rogers \"a""#;
1250
1251        assert_eq!(
1252            parse(&data[..]).unwrap().token_tape,
1253            vec![
1254                TextToken::Unquoted(Scalar::new(b"name")),
1255                TextToken::Quoted(Scalar::new(br#"J Rogers \"a"#)),
1256            ]
1257        );
1258    }
1259
1260    #[test]
1261    fn test_escaped_quotes_crazy() {
1262        let data = br#"custom_name="THE !@#$%^&*( '\"LEGION\"')""#;
1263
1264        assert_eq!(
1265            parse(&data[..]).unwrap().token_tape,
1266            vec![
1267                TextToken::Unquoted(Scalar::new(b"custom_name")),
1268                TextToken::Quoted(Scalar::new(br#"THE !@#$%^&*( '\"LEGION\"')"#)),
1269            ]
1270        );
1271    }
1272
1273    #[test]
1274    fn test_quotes_with_escape_sequences() {
1275        // Preventative measures to ensure we don't regress on imperator color codes
1276        let data = b"custom_name=\"ab \x15D ( ID: 691 )\x15!\"";
1277        assert_eq!(
1278            parse(&data[..]).unwrap().token_tape,
1279            vec![
1280                TextToken::Unquoted(Scalar::new(b"custom_name")),
1281                TextToken::Quoted(Scalar::new(b"ab \x15D ( ID: 691 )\x15!")),
1282            ]
1283        );
1284    }
1285
1286    #[test]
1287    fn test_numbers_are_scalars() {
1288        let data = b"foo=1.000";
1289
1290        assert_eq!(
1291            parse(&data[..]).unwrap().token_tape,
1292            vec![
1293                TextToken::Unquoted(Scalar::new(b"foo")),
1294                TextToken::Unquoted(Scalar::new(b"1.000")),
1295            ]
1296        );
1297    }
1298
1299    #[test]
1300    fn test_object_event() {
1301        let data = b"foo={bar=qux}";
1302
1303        assert_eq!(
1304            parse(&data[..]).unwrap().token_tape,
1305            vec![
1306                TextToken::Unquoted(Scalar::new(b"foo")),
1307                TextToken::Object {
1308                    end: 4,
1309                    mixed: false
1310                },
1311                TextToken::Unquoted(Scalar::new(b"bar")),
1312                TextToken::Unquoted(Scalar::new(b"qux")),
1313                TextToken::End(1),
1314            ]
1315        );
1316    }
1317
1318    #[test]
1319    fn test_object_multi_field_event() {
1320        let data = b"foo={bar=1 qux=28}";
1321
1322        assert_eq!(
1323            parse(&data[..]).unwrap().token_tape,
1324            vec![
1325                TextToken::Unquoted(Scalar::new(b"foo")),
1326                TextToken::Object {
1327                    end: 6,
1328                    mixed: false
1329                },
1330                TextToken::Unquoted(Scalar::new(b"bar")),
1331                TextToken::Unquoted(Scalar::new(b"1")),
1332                TextToken::Unquoted(Scalar::new(b"qux")),
1333                TextToken::Unquoted(Scalar::new(b"28")),
1334                TextToken::End(1),
1335            ]
1336        );
1337    }
1338
1339    #[test]
1340    fn test_text_parser_tape() {
1341        let mut tape = TextTape::new();
1342
1343        let data = b"foo={bar=1 qux=28}";
1344        TextTape::parser()
1345            .parse_slice_into_tape(data, &mut tape)
1346            .unwrap();
1347
1348        assert_eq!(
1349            tape.tokens(),
1350            vec![
1351                TextToken::Unquoted(Scalar::new(b"foo")),
1352                TextToken::Object {
1353                    end: 6,
1354                    mixed: false
1355                },
1356                TextToken::Unquoted(Scalar::new(b"bar")),
1357                TextToken::Unquoted(Scalar::new(b"1")),
1358                TextToken::Unquoted(Scalar::new(b"qux")),
1359                TextToken::Unquoted(Scalar::new(b"28")),
1360                TextToken::End(1),
1361            ]
1362        );
1363
1364        let data2 = b"foo2={bar2=3 qux2=29}";
1365        TextTape::parser()
1366            .parse_slice_into_tape(data2, &mut tape)
1367            .unwrap();
1368
1369        assert_eq!(
1370            tape.tokens(),
1371            vec![
1372                TextToken::Unquoted(Scalar::new(b"foo2")),
1373                TextToken::Object {
1374                    end: 6,
1375                    mixed: false
1376                },
1377                TextToken::Unquoted(Scalar::new(b"bar2")),
1378                TextToken::Unquoted(Scalar::new(b"3")),
1379                TextToken::Unquoted(Scalar::new(b"qux2")),
1380                TextToken::Unquoted(Scalar::new(b"29")),
1381                TextToken::End(1),
1382            ]
1383        );
1384    }
1385
1386    #[test]
1387    fn test_array_event() {
1388        let data = b"versions={\r\n\t\"1.28.3.0\"\r\n}";
1389
1390        assert_eq!(
1391            parse(&data[..]).unwrap().token_tape,
1392            vec![
1393                TextToken::Unquoted(Scalar::new(b"versions")),
1394                TextToken::Array {
1395                    end: 3,
1396                    mixed: false
1397                },
1398                TextToken::Quoted(Scalar::new(b"1.28.3.0")),
1399                TextToken::End(1),
1400            ]
1401        );
1402    }
1403
1404    #[test]
1405    fn test_array_multievent() {
1406        let data = b"versions={\r\n\t\"1.28.3.0\"\r\n foo}";
1407
1408        assert_eq!(
1409            parse(&data[..]).unwrap().token_tape,
1410            vec![
1411                TextToken::Unquoted(Scalar::new(b"versions")),
1412                TextToken::Array {
1413                    end: 4,
1414                    mixed: false
1415                },
1416                TextToken::Quoted(Scalar::new(b"1.28.3.0")),
1417                TextToken::Unquoted(Scalar::new(b"foo")),
1418                TextToken::End(1),
1419            ]
1420        );
1421    }
1422
1423    #[test]
1424    fn test_no_equal_object_event() {
1425        let data = b"foo{bar=qux}";
1426
1427        assert_eq!(
1428            parse(&data[..]).unwrap().token_tape,
1429            vec![
1430                TextToken::Unquoted(Scalar::new(b"foo")),
1431                TextToken::Object {
1432                    end: 4,
1433                    mixed: false
1434                },
1435                TextToken::Unquoted(Scalar::new(b"bar")),
1436                TextToken::Unquoted(Scalar::new(b"qux")),
1437                TextToken::End(1),
1438            ]
1439        );
1440    }
1441
1442    #[test]
1443    fn test_no_equal_object_field() {
1444        let data = br#"
1445        dlc002 = {
1446            name = "Arachnoid Portrait Pack"
1447            steam_id = "447680"
1448            gog_store_id = ""
1449            paradoxplaza_store_url ""
1450            category = "content_pack"
1451            show = no
1452            recommendations = {}
1453        }"#;
1454        assert_eq!(
1455            parse(&data[..]).unwrap().token_tape,
1456            vec![
1457                TextToken::Unquoted(Scalar::new(b"dlc002")),
1458                TextToken::Object {
1459                    end: 21,
1460                    mixed: false
1461                },
1462                TextToken::Unquoted(Scalar::new(b"name")),
1463                TextToken::Quoted(Scalar::new(b"Arachnoid Portrait Pack")),
1464                TextToken::Unquoted(Scalar::new(b"steam_id")),
1465                TextToken::Quoted(Scalar::new(b"447680")),
1466                TextToken::Unquoted(Scalar::new(b"gog_store_id")),
1467                TextToken::Quoted(Scalar::new(b"")),
1468                TextToken::MixedContainer,
1469                TextToken::Unquoted(Scalar::new(b"paradoxplaza_store_url")),
1470                TextToken::Quoted(Scalar::new(b"")),
1471                TextToken::Unquoted(Scalar::new(b"category")),
1472                TextToken::Operator(Operator::Equal),
1473                TextToken::Quoted(Scalar::new(b"content_pack")),
1474                TextToken::Unquoted(Scalar::new(b"show")),
1475                TextToken::Operator(Operator::Equal),
1476                TextToken::Unquoted(Scalar::new(b"no")),
1477                TextToken::Unquoted(Scalar::new(b"recommendations")),
1478                TextToken::Operator(Operator::Equal),
1479                TextToken::Array {
1480                    end: 20,
1481                    mixed: false
1482                },
1483                TextToken::End(19),
1484                TextToken::End(1),
1485            ]
1486        );
1487    }
1488
1489    #[test]
1490    fn test_empty_array() {
1491        let data = b"discovered_by={}";
1492
1493        assert_eq!(
1494            parse(&data[..]).unwrap().token_tape,
1495            vec![
1496                TextToken::Unquoted(Scalar::new(b"discovered_by")),
1497                TextToken::Array {
1498                    end: 2,
1499                    mixed: false
1500                },
1501                TextToken::End(1),
1502            ]
1503        );
1504    }
1505
1506    #[test]
1507    fn test_array_of_objects() {
1508        let data = b"stats={{id=0 type=general} {id=1 type=admiral}}";
1509
1510        assert_eq!(
1511            parse(&data[..]).unwrap().token_tape,
1512            vec![
1513                TextToken::Unquoted(Scalar::new(b"stats")),
1514                TextToken::Array {
1515                    end: 14,
1516                    mixed: false
1517                },
1518                TextToken::Object {
1519                    end: 7,
1520                    mixed: false
1521                },
1522                TextToken::Unquoted(Scalar::new(b"id")),
1523                TextToken::Unquoted(Scalar::new(b"0")),
1524                TextToken::Unquoted(Scalar::new(b"type")),
1525                TextToken::Unquoted(Scalar::new(b"general")),
1526                TextToken::End(2),
1527                TextToken::Object {
1528                    end: 13,
1529                    mixed: false
1530                },
1531                TextToken::Unquoted(Scalar::new(b"id")),
1532                TextToken::Unquoted(Scalar::new(b"1")),
1533                TextToken::Unquoted(Scalar::new(b"type")),
1534                TextToken::Unquoted(Scalar::new(b"admiral")),
1535                TextToken::End(8),
1536                TextToken::End(1),
1537            ]
1538        );
1539    }
1540
1541    #[test]
1542    fn test_empty_objects() {
1543        let data = b"history={{} 1629.11.10={core=AAA}}";
1544
1545        assert_eq!(
1546            parse(&data[..]).unwrap().token_tape,
1547            vec![
1548                TextToken::Unquoted(Scalar::new(b"history")),
1549                TextToken::Object {
1550                    end: 7,
1551                    mixed: false
1552                },
1553                TextToken::Unquoted(Scalar::new(b"1629.11.10")),
1554                TextToken::Object {
1555                    end: 6,
1556                    mixed: false
1557                },
1558                TextToken::Unquoted(Scalar::new(b"core")),
1559                TextToken::Unquoted(Scalar::new(b"AAA")),
1560                TextToken::End(3),
1561                TextToken::End(1),
1562            ]
1563        );
1564    }
1565
1566    #[test]
1567    fn test_empty_multi_objects() {
1568        let data = b"history={{} {} 1629.11.10={core=AAA}}";
1569
1570        assert_eq!(
1571            parse(&data[..]).unwrap().token_tape,
1572            vec![
1573                TextToken::Unquoted(Scalar::new(b"history")),
1574                TextToken::Object {
1575                    end: 7,
1576                    mixed: false
1577                },
1578                TextToken::Unquoted(Scalar::new(b"1629.11.10")),
1579                TextToken::Object {
1580                    end: 6,
1581                    mixed: false
1582                },
1583                TextToken::Unquoted(Scalar::new(b"core")),
1584                TextToken::Unquoted(Scalar::new(b"AAA")),
1585                TextToken::End(3),
1586                TextToken::End(1),
1587            ]
1588        );
1589    }
1590
1591    #[test]
1592    fn test_empty_objects2() {
1593        let data = b"foo={bar=val {}} me=you";
1594
1595        assert_eq!(
1596            parse(&data[..]).unwrap().token_tape,
1597            vec![
1598                TextToken::Unquoted(Scalar::new(b"foo")),
1599                TextToken::Object {
1600                    end: 4,
1601                    mixed: false
1602                },
1603                TextToken::Unquoted(Scalar::new(b"bar")),
1604                TextToken::Unquoted(Scalar::new(b"val")),
1605                TextToken::End(1),
1606                TextToken::Unquoted(Scalar::new(b"me")),
1607                TextToken::Unquoted(Scalar::new(b"you")),
1608            ]
1609        );
1610    }
1611
1612    #[test]
1613    fn test_empty_objects3() {
1614        let data = b"foo={bar=val { } a=b} me=you";
1615
1616        assert_eq!(
1617            parse(&data[..]).unwrap().token_tape,
1618            vec![
1619                TextToken::Unquoted(Scalar::new(b"foo")),
1620                TextToken::Object {
1621                    end: 6,
1622                    mixed: false
1623                },
1624                TextToken::Unquoted(Scalar::new(b"bar")),
1625                TextToken::Unquoted(Scalar::new(b"val")),
1626                TextToken::Unquoted(Scalar::new(b"a")),
1627                TextToken::Unquoted(Scalar::new(b"b")),
1628                TextToken::End(1),
1629                TextToken::Unquoted(Scalar::new(b"me")),
1630                TextToken::Unquoted(Scalar::new(b"you")),
1631            ]
1632        );
1633    }
1634
1635    #[test]
1636    fn test_spanning_objects() {
1637        let data = b"army={name=abc} army={name=def}";
1638
1639        assert_eq!(
1640            parse(&data[..]).unwrap().token_tape,
1641            vec![
1642                TextToken::Unquoted(Scalar::new(b"army")),
1643                TextToken::Object {
1644                    end: 4,
1645                    mixed: false
1646                },
1647                TextToken::Unquoted(Scalar::new(b"name")),
1648                TextToken::Unquoted(Scalar::new(b"abc")),
1649                TextToken::End(1),
1650                TextToken::Unquoted(Scalar::new(b"army")),
1651                TextToken::Object {
1652                    end: 9,
1653                    mixed: false
1654                },
1655                TextToken::Unquoted(Scalar::new(b"name")),
1656                TextToken::Unquoted(Scalar::new(b"def")),
1657                TextToken::End(6),
1658            ]
1659        );
1660    }
1661
1662    #[test]
1663    fn test_regression() {
1664        let data = [0, 32, 34, 0];
1665        assert!(parse(&data[..]).is_err());
1666    }
1667
1668    #[test]
1669    fn test_regression2() {
1670        let data = [0, 4, 33, 0];
1671        let _ = parse(&data[..]);
1672    }
1673
1674    #[test]
1675    #[cfg_attr(miri, ignore)] // too slow
1676    fn test_too_heavily_nested() {
1677        let mut data = Vec::new();
1678        data.extend_from_slice(b"foo=");
1679        for _ in 0..100000 {
1680            data.extend_from_slice(b"{");
1681        }
1682        assert!(parse(&data[..]).is_err());
1683    }
1684
1685    #[test]
1686    fn test_no_ws_comment() {
1687        let data = b"foo=abc#def\nbar=qux";
1688
1689        assert_eq!(
1690            parse(&data[..]).unwrap().token_tape,
1691            vec![
1692                TextToken::Unquoted(Scalar::new(b"foo")),
1693                TextToken::Unquoted(Scalar::new(b"abc")),
1694                TextToken::Unquoted(Scalar::new(b"bar")),
1695                TextToken::Unquoted(Scalar::new(b"qux")),
1696            ]
1697        );
1698    }
1699
1700    #[test]
1701    fn test_bom() {
1702        let data = b"\xef\xbb\xbf#hello";
1703        let tape = parse(&data[..]).unwrap();
1704
1705        assert_eq!(tape.token_tape, vec![]);
1706        assert!(tape.utf8_bom);
1707    }
1708
1709    #[test]
1710    fn test_period_in_identifiers() {
1711        let data = b"flavor_tur.8=yes";
1712
1713        assert_eq!(
1714            parse(&data[..]).unwrap().token_tape,
1715            vec![
1716                TextToken::Unquoted(Scalar::new(b"flavor_tur.8")),
1717                TextToken::Unquoted(Scalar::new(b"yes")),
1718            ]
1719        );
1720    }
1721
1722    #[test]
1723    fn test_dashed_identifiers() {
1724        // From stellaris saves
1725        let data = b"dashed-identifier=yes";
1726
1727        assert_eq!(
1728            parse(&data[..]).unwrap().token_tape,
1729            vec![
1730                TextToken::Unquoted(Scalar::new(b"dashed-identifier")),
1731                TextToken::Unquoted(Scalar::new(b"yes")),
1732            ]
1733        );
1734    }
1735
1736    #[test]
1737    fn test_colon_values() {
1738        let data = b"province_id = event_target:agenda_province";
1739
1740        assert_eq!(
1741            parse(&data[..]).unwrap().token_tape,
1742            vec![
1743                TextToken::Unquoted(Scalar::new(b"province_id")),
1744                TextToken::Unquoted(Scalar::new(b"event_target:agenda_province")),
1745            ]
1746        );
1747    }
1748
1749    #[test]
1750    fn test_parameter_syntax_with_values() {
1751        // the new syntax to pass parameters to script values is explained in
1752        // stellaris: common/script_values/00_script_values.txt
1753        let data = b"mult = value:job_weights_research_modifier|JOB|head_researcher|";
1754        assert_eq!(
1755            parse(&data[..]).unwrap().token_tape,
1756            vec![
1757                TextToken::Unquoted(Scalar::new(b"mult")),
1758                TextToken::Unquoted(Scalar::new(
1759                    b"value:job_weights_research_modifier|JOB|head_researcher|"
1760                )),
1761            ]
1762        );
1763    }
1764
1765    #[test]
1766    fn test_variables() {
1767        let data = b"@planet_standard_scale = 11";
1768
1769        assert_eq!(
1770            parse(&data[..]).unwrap().token_tape,
1771            vec![
1772                TextToken::Unquoted(Scalar::new(b"@planet_standard_scale")),
1773                TextToken::Unquoted(Scalar::new(b"11")),
1774            ]
1775        );
1776    }
1777
1778    #[test]
1779    fn test_interpolated_variable() {
1780        let data = b"position = { @[1-leopard_x] @leopard_y }";
1781        assert_eq!(
1782            parse(&data[..]).unwrap().token_tape,
1783            vec![
1784                TextToken::Unquoted(Scalar::new(b"position")),
1785                TextToken::Array {
1786                    end: 4,
1787                    mixed: false
1788                },
1789                TextToken::Unquoted(Scalar::new(b"@[1-leopard_x]")),
1790                TextToken::Unquoted(Scalar::new(b"@leopard_y")),
1791                TextToken::End(1),
1792            ]
1793        );
1794    }
1795
1796    #[test]
1797    fn test_variable_value() {
1798        let data = b"window_name = @default_window_name";
1799        assert_eq!(
1800            parse(&data[..]).unwrap().token_tape,
1801            vec![
1802                TextToken::Unquoted(Scalar::new(b"window_name")),
1803                TextToken::Unquoted(Scalar::new(b"@default_window_name")),
1804            ]
1805        );
1806    }
1807
1808    #[test]
1809    fn test_equal_identifier() {
1810        let data = br#"=="bar""#;
1811
1812        assert_eq!(
1813            parse(&data[..]).unwrap().token_tape,
1814            vec![
1815                TextToken::Unquoted(Scalar::new(b"=")),
1816                TextToken::Quoted(Scalar::new(b"bar")),
1817            ]
1818        );
1819    }
1820
1821    #[test]
1822    #[cfg_attr(miri, ignore)] // too slow
1823    fn test_many_line_comment() {
1824        let mut data = Vec::new();
1825        data.extend_from_slice(b"foo=1.000\n");
1826        for _ in 0..100000 {
1827            data.extend_from_slice(b"# this is a comment\n");
1828        }
1829        data.extend_from_slice(b"foo=2.000\n");
1830
1831        assert_eq!(
1832            parse(&data[..]).unwrap().token_tape,
1833            vec![
1834                TextToken::Unquoted(Scalar::new(b"foo")),
1835                TextToken::Unquoted(Scalar::new(b"1.000")),
1836                TextToken::Unquoted(Scalar::new(b"foo")),
1837                TextToken::Unquoted(Scalar::new(b"2.000")),
1838            ]
1839        );
1840    }
1841
1842    #[test]
1843    fn test_terminating_comment() {
1844        let data = b"# boo\r\n# baa\r\nfoo=a\r\n# bee";
1845        assert_eq!(
1846            parse(&data[..]).unwrap().token_tape,
1847            vec![
1848                TextToken::Unquoted(Scalar::new(b"foo")),
1849                TextToken::Unquoted(Scalar::new(b"a")),
1850            ]
1851        );
1852    }
1853
1854    #[test]
1855    fn test_rgb_trick() {
1856        let data = b"name = rgb ";
1857
1858        assert_eq!(
1859            parse(&data[..]).unwrap().token_tape,
1860            vec![
1861                TextToken::Unquoted(Scalar::new(b"name")),
1862                TextToken::Unquoted(Scalar::new(b"rgb")),
1863            ]
1864        );
1865    }
1866
1867    #[test]
1868    fn test_rgb_trick2() {
1869        let data = b"name = rgb type = 4713";
1870
1871        assert_eq!(
1872            parse(&data[..]).unwrap().token_tape,
1873            vec![
1874                TextToken::Unquoted(Scalar::new(b"name")),
1875                TextToken::Unquoted(Scalar::new(b"rgb")),
1876                TextToken::Unquoted(Scalar::new(b"type")),
1877                TextToken::Unquoted(Scalar::new(b"4713")),
1878            ]
1879        );
1880    }
1881
1882    #[test]
1883    fn test_rgb_trick3() {
1884        let data = b"name = rgbeffect";
1885
1886        assert_eq!(
1887            parse(&data[..]).unwrap().token_tape,
1888            vec![
1889                TextToken::Unquoted(Scalar::new(b"name")),
1890                TextToken::Unquoted(Scalar::new(b"rgbeffect")),
1891            ]
1892        );
1893    }
1894
1895    #[test]
1896    fn test_rgb() {
1897        let data = b"color = rgb { 100 200 150 } ";
1898
1899        assert_eq!(
1900            parse(&data[..]).unwrap().token_tape,
1901            vec![
1902                TextToken::Unquoted(Scalar::new(b"color")),
1903                TextToken::Header(Scalar::new(b"rgb")),
1904                TextToken::Array {
1905                    end: 6,
1906                    mixed: false
1907                },
1908                TextToken::Unquoted(Scalar::new(b"100")),
1909                TextToken::Unquoted(Scalar::new(b"200")),
1910                TextToken::Unquoted(Scalar::new(b"150")),
1911                TextToken::End(2),
1912            ]
1913        );
1914    }
1915
1916    #[test]
1917    fn test_hsv_trick() {
1918        let data = b"name = hsv ";
1919
1920        assert_eq!(
1921            parse(&data[..]).unwrap().token_tape,
1922            vec![
1923                TextToken::Unquoted(Scalar::new(b"name")),
1924                TextToken::Unquoted(Scalar::new(b"hsv")),
1925            ]
1926        );
1927    }
1928
1929    #[test]
1930    fn test_hsv_trick2() {
1931        let data = b"name = hsv type = 4713";
1932
1933        assert_eq!(
1934            parse(&data[..]).unwrap().token_tape,
1935            vec![
1936                TextToken::Unquoted(Scalar::new(b"name")),
1937                TextToken::Unquoted(Scalar::new(b"hsv")),
1938                TextToken::Unquoted(Scalar::new(b"type")),
1939                TextToken::Unquoted(Scalar::new(b"4713")),
1940            ]
1941        );
1942    }
1943
1944    #[test]
1945    fn test_hsv_trick3() {
1946        let data = b"name = hsveffect";
1947
1948        assert_eq!(
1949            parse(&data[..]).unwrap().token_tape,
1950            vec![
1951                TextToken::Unquoted(Scalar::new(b"name")),
1952                TextToken::Unquoted(Scalar::new(b"hsveffect")),
1953            ]
1954        );
1955    }
1956
1957    #[test]
1958    fn test_hsv() {
1959        let data = b"color = hsv { 0.43 0.86 0.61 } ";
1960
1961        assert_eq!(
1962            parse(&data[..]).unwrap().token_tape,
1963            vec![
1964                TextToken::Unquoted(Scalar::new(b"color")),
1965                TextToken::Header(Scalar::new(b"hsv")),
1966                TextToken::Array {
1967                    end: 6,
1968                    mixed: false
1969                },
1970                TextToken::Unquoted(Scalar::new(b"0.43")),
1971                TextToken::Unquoted(Scalar::new(b"0.86")),
1972                TextToken::Unquoted(Scalar::new(b"0.61")),
1973                TextToken::End(2),
1974            ]
1975        );
1976    }
1977
1978    #[test]
1979    fn test_hsv360() {
1980        let data = b"color = hsv360{ 25 75 63 }";
1981
1982        assert_eq!(
1983            parse(&data[..]).unwrap().token_tape,
1984            vec![
1985                TextToken::Unquoted(Scalar::new(b"color")),
1986                TextToken::Header(Scalar::new(b"hsv360")),
1987                TextToken::Array {
1988                    end: 6,
1989                    mixed: false
1990                },
1991                TextToken::Unquoted(Scalar::new(b"25")),
1992                TextToken::Unquoted(Scalar::new(b"75")),
1993                TextToken::Unquoted(Scalar::new(b"63")),
1994                TextToken::End(2),
1995            ]
1996        );
1997    }
1998
1999    #[test]
2000    fn test_cylindrical() {
2001        let data = b"color = cylindrical{ 150 3 0 }";
2002
2003        assert_eq!(
2004            parse(&data[..]).unwrap().token_tape,
2005            vec![
2006                TextToken::Unquoted(Scalar::new(b"color")),
2007                TextToken::Header(Scalar::new(b"cylindrical")),
2008                TextToken::Array {
2009                    end: 6,
2010                    mixed: false
2011                },
2012                TextToken::Unquoted(Scalar::new(b"150")),
2013                TextToken::Unquoted(Scalar::new(b"3")),
2014                TextToken::Unquoted(Scalar::new(b"0")),
2015                TextToken::End(2),
2016            ]
2017        );
2018    }
2019
2020    #[test]
2021    fn test_hex() {
2022        let data = b"color = hex { aabbccdd }";
2023
2024        assert_eq!(
2025            parse(&data[..]).unwrap().token_tape,
2026            vec![
2027                TextToken::Unquoted(Scalar::new(b"color")),
2028                TextToken::Header(Scalar::new(b"hex")),
2029                TextToken::Array {
2030                    end: 4,
2031                    mixed: false
2032                },
2033                TextToken::Unquoted(Scalar::new(b"aabbccdd")),
2034                TextToken::End(2),
2035            ]
2036        );
2037    }
2038
2039    #[test]
2040    fn test_list_header() {
2041        let data = b"mild_winter = LIST { 3700 3701 }";
2042
2043        assert_eq!(
2044            parse(&data[..]).unwrap().token_tape,
2045            vec![
2046                TextToken::Unquoted(Scalar::new(b"mild_winter")),
2047                TextToken::Header(Scalar::new(b"LIST")),
2048                TextToken::Array {
2049                    end: 5,
2050                    mixed: false
2051                },
2052                TextToken::Unquoted(Scalar::new(b"3700")),
2053                TextToken::Unquoted(Scalar::new(b"3701")),
2054                TextToken::End(2),
2055            ]
2056        );
2057    }
2058
2059    // #[test]
2060    // fn test_hidden_object_needs_key() {
2061    //     let data = b"a{{}=}";
2062    //     assert!(parse(&data[..]).is_err());
2063    // }
2064
2065    // #[test]
2066    // fn test_objects_in_hidden_objects_not_supported() {
2067    //     let data = b"u{1 a={0=1}";
2068    //     assert!(parse(&data[..]).is_err());
2069    // }
2070
2071    // #[test]
2072    // fn test_hidden_objects_with_headers_not_supported() {
2073    //     let data = b"s{{c d=a{b=}}";
2074    //     assert!(TextTape::from_slice(&data[..]).is_err());
2075    // }
2076
2077    #[test]
2078    fn test_operator_early_eof() {
2079        let data = b"a{b=}";
2080        assert!(TextTape::from_slice(&data[..]).is_err());
2081    }
2082
2083    #[test]
2084    fn test_less_than_equal_operator() {
2085        let data = b"scope:attacker.primary_title.tier <= tier_county";
2086        assert_eq!(
2087            parse(&data[..]).unwrap().token_tape,
2088            vec![
2089                TextToken::Unquoted(Scalar::new(b"scope:attacker.primary_title.tier")),
2090                TextToken::Operator(Operator::LessThanEqual),
2091                TextToken::Unquoted(Scalar::new(b"tier_county")),
2092            ]
2093        );
2094    }
2095
2096    #[test]
2097    fn test_less_than_operator() {
2098        let data = b"count < 2";
2099        assert_eq!(
2100            parse(&data[..]).unwrap().token_tape,
2101            vec![
2102                TextToken::Unquoted(Scalar::new(b"count")),
2103                TextToken::Operator(Operator::LessThan),
2104                TextToken::Unquoted(Scalar::new(b"2")),
2105            ]
2106        );
2107    }
2108
2109    #[test]
2110    fn test_greater_than_operator() {
2111        let data = b"age > 16";
2112        assert_eq!(
2113            parse(&data[..]).unwrap().token_tape,
2114            vec![
2115                TextToken::Unquoted(Scalar::new(b"age")),
2116                TextToken::Operator(Operator::GreaterThan),
2117                TextToken::Unquoted(Scalar::new(b"16")),
2118            ]
2119        );
2120    }
2121
2122    #[test]
2123    fn test_greater_than_equal_operator() {
2124        let data = b"intrigue >= high_skill_rating";
2125        assert_eq!(
2126            parse(&data[..]).unwrap().token_tape,
2127            vec![
2128                TextToken::Unquoted(Scalar::new(b"intrigue")),
2129                TextToken::Operator(Operator::GreaterThanEqual),
2130                TextToken::Unquoted(Scalar::new(b"high_skill_rating")),
2131            ]
2132        );
2133    }
2134
2135    #[test]
2136    fn test_not_equal_operator() {
2137        let data = b"count != 2";
2138        assert_eq!(
2139            parse(&data[..]).unwrap().token_tape,
2140            vec![
2141                TextToken::Unquoted(Scalar::new(b"count")),
2142                TextToken::Operator(Operator::NotEqual),
2143                TextToken::Unquoted(Scalar::new(b"2")),
2144            ]
2145        );
2146    }
2147
2148    #[test]
2149    fn test_not_exact_operator() {
2150        let data = b"start_date == 1066.9.15";
2151        assert_eq!(
2152            parse(&data[..]).unwrap().token_tape,
2153            vec![
2154                TextToken::Unquoted(Scalar::new(b"start_date")),
2155                TextToken::Operator(Operator::Exact),
2156                TextToken::Unquoted(Scalar::new(b"1066.9.15")),
2157            ]
2158        );
2159    }
2160
2161    #[test]
2162    fn test_operator_in_object() {
2163        let data = b"value={ date < 1941.7.7 is_preparing=true }";
2164        assert_eq!(
2165            parse(&data[..]).unwrap().token_tape,
2166            vec![
2167                TextToken::Unquoted(Scalar::new(b"value")),
2168                TextToken::Object {
2169                    end: 7,
2170                    mixed: false
2171                },
2172                TextToken::Unquoted(Scalar::new(b"date")),
2173                TextToken::Operator(Operator::LessThan),
2174                TextToken::Unquoted(Scalar::new(b"1941.7.7")),
2175                TextToken::Unquoted(Scalar::new(b"is_preparing")),
2176                TextToken::Unquoted(Scalar::new(b"true")),
2177                TextToken::End(1),
2178            ]
2179        );
2180    }
2181
2182    #[test]
2183    fn test_exists_operator() {
2184        let data = b"c:RUS ?= this";
2185
2186        assert_eq!(
2187            parse(&data[..]).unwrap().token_tape,
2188            vec![
2189                TextToken::Unquoted(Scalar::new(b"c:RUS")),
2190                TextToken::Operator(Operator::Exists),
2191                TextToken::Unquoted(Scalar::new(b"this")),
2192            ]
2193        );
2194    }
2195
2196    #[test]
2197    fn test_exists_operator_in_object_first_pair() {
2198        let data = b"obj = { foo ?= 1 }";
2199
2200        let result = parse(&data[..]).unwrap().token_tape;
2201
2202        assert_eq!(
2203            result,
2204            vec![
2205                TextToken::Unquoted(Scalar::new(b"obj")),
2206                TextToken::Object {
2207                    end: 5,
2208                    mixed: false
2209                },
2210                TextToken::Unquoted(Scalar::new(b"foo")),
2211                TextToken::Operator(Operator::Exists),
2212                TextToken::Unquoted(Scalar::new(b"1")),
2213                TextToken::End(1),
2214            ]
2215        );
2216    }
2217
2218    #[test]
2219    fn test_not_equal_operator_in_object_first_pair() {
2220        let data = b"obj = { foo != 1 }";
2221
2222        let result = parse(&data[..]).unwrap().token_tape;
2223
2224        assert_eq!(
2225            result,
2226            vec![
2227                TextToken::Unquoted(Scalar::new(b"obj")),
2228                TextToken::Object {
2229                    end: 5,
2230                    mixed: false
2231                },
2232                TextToken::Unquoted(Scalar::new(b"foo")),
2233                TextToken::Operator(Operator::NotEqual),
2234                TextToken::Unquoted(Scalar::new(b"1")),
2235                TextToken::End(1),
2236            ]
2237        );
2238    }
2239
2240    #[test]
2241    fn test_extraneous_closing_bracket() {
2242        let data = b"foo = { bar } } ";
2243
2244        assert_eq!(
2245            parse(&data[..]).unwrap().token_tape,
2246            vec![
2247                TextToken::Unquoted(Scalar::new(b"foo")),
2248                TextToken::Array {
2249                    end: 3,
2250                    mixed: false
2251                },
2252                TextToken::Unquoted(Scalar::new(b"bar")),
2253                TextToken::End(1),
2254            ]
2255        );
2256    }
2257
2258    #[test]
2259    fn test_extraneous_closing_bracket2() {
2260        let data = b"a{} b=r}";
2261        assert_eq!(
2262            parse(&data[..]).unwrap().token_tape,
2263            vec![
2264                TextToken::Unquoted(Scalar::new(b"a")),
2265                TextToken::Array {
2266                    end: 2,
2267                    mixed: false
2268                },
2269                TextToken::End(1),
2270                TextToken::Unquoted(Scalar::new(b"b")),
2271                TextToken::Unquoted(Scalar::new(b"r")),
2272            ]
2273        );
2274    }
2275
2276    #[test]
2277    fn test_extraneous_closing_bracket3() {
2278        let data = b"a=c}}";
2279        assert_eq!(
2280            parse(&data[..]).unwrap().token_tape,
2281            vec![
2282                TextToken::Unquoted(Scalar::new(b"a")),
2283                TextToken::Unquoted(Scalar::new(b"c")),
2284            ]
2285        );
2286    }
2287
2288    #[test]
2289    fn test_extraneous_bracket() {
2290        // from eu4 verona.txt
2291        let data = br#"color = { 121 163 114 } } army_names = {"Armata di $PROVINCE$"}"#;
2292
2293        assert_eq!(
2294            parse(&data[..]).unwrap().token_tape,
2295            vec![
2296                TextToken::Unquoted(Scalar::new(b"color")),
2297                TextToken::Array {
2298                    end: 5,
2299                    mixed: false
2300                },
2301                TextToken::Unquoted(Scalar::new(b"121")),
2302                TextToken::Unquoted(Scalar::new(b"163")),
2303                TextToken::Unquoted(Scalar::new(b"114")),
2304                TextToken::End(1),
2305                TextToken::Unquoted(Scalar::new(b"army_names")),
2306                TextToken::Array {
2307                    end: 9,
2308                    mixed: false
2309                },
2310                TextToken::Quoted(Scalar::new(b"Armata di $PROVINCE$")),
2311                TextToken::End(7),
2312            ]
2313        );
2314    }
2315
2316    #[test]
2317    fn test_missing_bracket() {
2318        let data = br#"BRA_GAR_01 = { ordered = { 1 = { "%da Divisao de Infantaria" } }"#;
2319        assert_eq!(
2320            parse(&data[..]).unwrap().token_tape,
2321            vec![
2322                TextToken::Unquoted(Scalar::new(b"BRA_GAR_01")),
2323                TextToken::Object {
2324                    end: 9,
2325                    mixed: false
2326                },
2327                TextToken::Unquoted(Scalar::new(b"ordered")),
2328                TextToken::Object {
2329                    end: 8,
2330                    mixed: false
2331                },
2332                TextToken::Unquoted(Scalar::new(b"1")),
2333                TextToken::Array {
2334                    end: 7,
2335                    mixed: false
2336                },
2337                TextToken::Quoted(Scalar::new(b"%da Divisao de Infantaria")),
2338                TextToken::End(5),
2339                TextToken::End(3),
2340                TextToken::End(1),
2341            ]
2342        );
2343    }
2344
2345    #[test]
2346    fn test_unquoted_non_ascii() {
2347        // More vic2 shenanigans
2348        let data = b"jean_jaur\xe8s = bar ";
2349
2350        assert_eq!(
2351            parse(&data[..]).unwrap().token_tape,
2352            vec![
2353                TextToken::Unquoted(Scalar::new(b"jean_jaur\xe8s")),
2354                TextToken::Unquoted(Scalar::new(b"bar")),
2355            ]
2356        );
2357    }
2358
2359    #[test]
2360    fn test_parameter_value() {
2361        let data = b"generate_advisor = { [[effect] $effect$ ] }";
2362        assert_eq!(
2363            parse(&data[..]).unwrap().token_tape,
2364            vec![
2365                TextToken::Unquoted(Scalar::new(b"generate_advisor")),
2366                TextToken::Object {
2367                    end: 4,
2368                    mixed: false
2369                },
2370                TextToken::Parameter(Scalar::new(b"effect")),
2371                TextToken::Unquoted(Scalar::new(b"$effect$")),
2372                TextToken::End(1),
2373            ]
2374        );
2375    }
2376
2377    #[test]
2378    fn test_parameter_object() {
2379        let data = b"generate_advisor = { [[scaled_skill] a=b ] }";
2380        assert_eq!(
2381            parse(&data[..]).unwrap().token_tape,
2382            vec![
2383                TextToken::Unquoted(Scalar::new(b"generate_advisor")),
2384                TextToken::Object {
2385                    end: 7,
2386                    mixed: false
2387                },
2388                TextToken::Parameter(Scalar::new(b"scaled_skill")),
2389                TextToken::Object {
2390                    end: 6,
2391                    mixed: false
2392                },
2393                TextToken::Unquoted(Scalar::new(b"a")),
2394                TextToken::Unquoted(Scalar::new(b"b")),
2395                TextToken::End(3),
2396                TextToken::End(1),
2397            ]
2398        );
2399    }
2400
2401    #[test]
2402    fn test_parameter_object2() {
2403        let data = b"generate_advisor = { [[add] if = { a=b }] }";
2404        assert_eq!(
2405            parse(&data[..]).unwrap().token_tape,
2406            vec![
2407                TextToken::Unquoted(Scalar::new(b"generate_advisor")),
2408                TextToken::Object {
2409                    end: 10,
2410                    mixed: false
2411                },
2412                TextToken::Parameter(Scalar::new(b"add")),
2413                TextToken::Object {
2414                    end: 9,
2415                    mixed: false
2416                },
2417                TextToken::Unquoted(Scalar::new(b"if")),
2418                TextToken::Object {
2419                    end: 8,
2420                    mixed: false
2421                },
2422                TextToken::Unquoted(Scalar::new(b"a")),
2423                TextToken::Unquoted(Scalar::new(b"b")),
2424                TextToken::End(5),
2425                TextToken::End(3),
2426                TextToken::End(1),
2427            ]
2428        );
2429    }
2430
2431    #[test]
2432    fn test_parameter_object3() {
2433        let data = b"generate_advisor = { [[add] if = { a=b }] [[remove] if={c=d}] }";
2434        assert_eq!(
2435            parse(&data[..]).unwrap().token_tape,
2436            vec![
2437                TextToken::Unquoted(Scalar::new(b"generate_advisor")),
2438                TextToken::Object {
2439                    end: 18,
2440                    mixed: false
2441                },
2442                TextToken::Parameter(Scalar::new(b"add")),
2443                TextToken::Object {
2444                    end: 9,
2445                    mixed: false
2446                },
2447                TextToken::Unquoted(Scalar::new(b"if")),
2448                TextToken::Object {
2449                    end: 8,
2450                    mixed: false
2451                },
2452                TextToken::Unquoted(Scalar::new(b"a")),
2453                TextToken::Unquoted(Scalar::new(b"b")),
2454                TextToken::End(5),
2455                TextToken::End(3),
2456                TextToken::Parameter(Scalar::new(b"remove")),
2457                TextToken::Object {
2458                    end: 17,
2459                    mixed: false
2460                },
2461                TextToken::Unquoted(Scalar::new(b"if")),
2462                TextToken::Object {
2463                    end: 16,
2464                    mixed: false
2465                },
2466                TextToken::Unquoted(Scalar::new(b"c")),
2467                TextToken::Unquoted(Scalar::new(b"d")),
2468                TextToken::End(13),
2469                TextToken::End(11),
2470                TextToken::End(1),
2471            ]
2472        );
2473    }
2474
2475    #[test]
2476    fn test_undefined_parameter_object() {
2477        let data = b"generate_advisor = { [[!scaled_skill] a=b ] }";
2478        assert_eq!(
2479            parse(&data[..]).unwrap().token_tape,
2480            vec![
2481                TextToken::Unquoted(Scalar::new(b"generate_advisor")),
2482                TextToken::Object {
2483                    end: 7,
2484                    mixed: false
2485                },
2486                TextToken::UndefinedParameter(Scalar::new(b"scaled_skill")),
2487                TextToken::Object {
2488                    end: 6,
2489                    mixed: false
2490                },
2491                TextToken::Unquoted(Scalar::new(b"a")),
2492                TextToken::Unquoted(Scalar::new(b"b")),
2493                TextToken::End(3),
2494                TextToken::End(1),
2495            ]
2496        );
2497    }
2498
2499    #[test]
2500    fn test_text_number_plus() {
2501        let data = b"pop_happiness = +0.10";
2502        assert_eq!(
2503            parse(&data[..]).unwrap().token_tape,
2504            vec![
2505                TextToken::Unquoted(Scalar::new(b"pop_happiness")),
2506                TextToken::Unquoted(Scalar::new(b"+0.10")),
2507            ]
2508        );
2509    }
2510
2511    #[test]
2512    fn test_skip_semicolon() {
2513        let data = b"value=\"win\"; a=b";
2514        assert_eq!(
2515            parse(&data[..]).unwrap().token_tape,
2516            vec![
2517                TextToken::Unquoted(Scalar::new(b"value")),
2518                TextToken::Quoted(Scalar::new(b"win")),
2519                TextToken::Unquoted(Scalar::new(b"a")),
2520                TextToken::Unquoted(Scalar::new(b"b")),
2521            ]
2522        );
2523    }
2524
2525    #[test]
2526    fn test_mixed_container_1() {
2527        let data = b"levels={a=b 10}";
2528        assert_eq!(
2529            parse(&data[..]).unwrap().token_tape,
2530            vec![
2531                TextToken::Unquoted(Scalar::new(b"levels")),
2532                TextToken::Object {
2533                    end: 6,
2534                    mixed: true
2535                },
2536                TextToken::Unquoted(Scalar::new(b"a")),
2537                TextToken::Unquoted(Scalar::new(b"b")),
2538                TextToken::MixedContainer,
2539                TextToken::Unquoted(Scalar::new(b"10")),
2540                TextToken::End(1),
2541            ]
2542        );
2543    }
2544
2545    #[test]
2546    fn test_mixed_container_2() {
2547        let data = b"levels={a=b 10 20}";
2548        assert_eq!(
2549            parse(&data[..]).unwrap().token_tape,
2550            vec![
2551                TextToken::Unquoted(Scalar::new(b"levels")),
2552                TextToken::Object {
2553                    end: 7,
2554                    mixed: true
2555                },
2556                TextToken::Unquoted(Scalar::new(b"a")),
2557                TextToken::Unquoted(Scalar::new(b"b")),
2558                TextToken::MixedContainer,
2559                TextToken::Unquoted(Scalar::new(b"10")),
2560                TextToken::Unquoted(Scalar::new(b"20")),
2561                TextToken::End(1),
2562            ]
2563        );
2564    }
2565
2566    #[test]
2567    fn test_mixed_container_3() {
2568        let data = b"levels={a=b 10 c=d}";
2569        assert_eq!(
2570            parse(&data[..]).unwrap().token_tape,
2571            vec![
2572                TextToken::Unquoted(Scalar::new(b"levels")),
2573                TextToken::Object {
2574                    end: 9,
2575                    mixed: true
2576                },
2577                TextToken::Unquoted(Scalar::new(b"a")),
2578                TextToken::Unquoted(Scalar::new(b"b")),
2579                TextToken::MixedContainer,
2580                TextToken::Unquoted(Scalar::new(b"10")),
2581                TextToken::Unquoted(Scalar::new(b"c")),
2582                TextToken::Operator(Operator::Equal),
2583                TextToken::Unquoted(Scalar::new(b"d")),
2584                TextToken::End(1),
2585            ]
2586        );
2587    }
2588
2589    #[test]
2590    fn test_mixed_container_4() {
2591        let data = b"levels={a=b 10 c=d 20}";
2592        assert_eq!(
2593            parse(&data[..]).unwrap().token_tape,
2594            vec![
2595                TextToken::Unquoted(Scalar::new(b"levels")),
2596                TextToken::Object {
2597                    end: 10,
2598                    mixed: true
2599                },
2600                TextToken::Unquoted(Scalar::new(b"a")),
2601                TextToken::Unquoted(Scalar::new(b"b")),
2602                TextToken::MixedContainer,
2603                TextToken::Unquoted(Scalar::new(b"10")),
2604                TextToken::Unquoted(Scalar::new(b"c")),
2605                TextToken::Operator(Operator::Equal),
2606                TextToken::Unquoted(Scalar::new(b"d")),
2607                TextToken::Unquoted(Scalar::new(b"20")),
2608                TextToken::End(1),
2609            ]
2610        );
2611    }
2612
2613    #[test]
2614    fn test_mixed_container_5() {
2615        let data = b"16778374={ levels={ 10 0=2 1=2 } }";
2616
2617        assert_eq!(
2618            parse(&data[..]).unwrap().token_tape,
2619            vec![
2620                TextToken::Unquoted(Scalar::new(b"16778374")),
2621                TextToken::Object {
2622                    end: 13,
2623                    mixed: false
2624                },
2625                TextToken::Unquoted(Scalar::new(b"levels")),
2626                TextToken::Array {
2627                    end: 12,
2628                    mixed: true
2629                },
2630                TextToken::Unquoted(Scalar::new(b"10")),
2631                TextToken::MixedContainer,
2632                TextToken::Unquoted(Scalar::new(b"0")),
2633                TextToken::Operator(Operator::Equal),
2634                TextToken::Unquoted(Scalar::new(b"2")),
2635                TextToken::Unquoted(Scalar::new(b"1")),
2636                TextToken::Operator(Operator::Equal),
2637                TextToken::Unquoted(Scalar::new(b"2")),
2638                TextToken::End(3),
2639                TextToken::End(1),
2640            ]
2641        );
2642    }
2643
2644    #[test]
2645    fn test_mixed_container_6() {
2646        let data = b"levels={ 10 0=2 1=2 } foo={bar=qux}";
2647        assert_eq!(
2648            parse(&data[..]).unwrap().token_tape,
2649            vec![
2650                TextToken::Unquoted(Scalar::new(b"levels")),
2651                TextToken::Array {
2652                    end: 10,
2653                    mixed: true
2654                },
2655                TextToken::Unquoted(Scalar::new(b"10")),
2656                TextToken::MixedContainer,
2657                TextToken::Unquoted(Scalar::new(b"0")),
2658                TextToken::Operator(Operator::Equal),
2659                TextToken::Unquoted(Scalar::new(b"2")),
2660                TextToken::Unquoted(Scalar::new(b"1")),
2661                TextToken::Operator(Operator::Equal),
2662                TextToken::Unquoted(Scalar::new(b"2")),
2663                TextToken::End(1),
2664                TextToken::Unquoted(Scalar::new(b"foo")),
2665                TextToken::Object {
2666                    end: 15,
2667                    mixed: false
2668                },
2669                TextToken::Unquoted(Scalar::new(b"bar")),
2670                TextToken::Unquoted(Scalar::new(b"qux")),
2671                TextToken::End(12),
2672            ]
2673        );
2674    }
2675
2676    #[test]
2677    fn test_mixed_container_7() {
2678        let data = br#"brittany_area = { #5
2679            color = { 118  99  151 }
2680            169 170 171 172 4384
2681        }"#;
2682
2683        assert_eq!(
2684            parse(&data[..]).unwrap().token_tape,
2685            vec![
2686                TextToken::Unquoted(Scalar::new(b"brittany_area")),
2687                TextToken::Object {
2688                    end: 14,
2689                    mixed: true
2690                },
2691                TextToken::Unquoted(Scalar::new(b"color")),
2692                TextToken::Array {
2693                    end: 7,
2694                    mixed: false
2695                },
2696                TextToken::Unquoted(Scalar::new(b"118")),
2697                TextToken::Unquoted(Scalar::new(b"99")),
2698                TextToken::Unquoted(Scalar::new(b"151")),
2699                TextToken::End(3),
2700                TextToken::MixedContainer,
2701                TextToken::Unquoted(Scalar::new(b"169")),
2702                TextToken::Unquoted(Scalar::new(b"170")),
2703                TextToken::Unquoted(Scalar::new(b"171")),
2704                TextToken::Unquoted(Scalar::new(b"172")),
2705                TextToken::Unquoted(Scalar::new(b"4384")),
2706                TextToken::End(1),
2707            ]
2708        );
2709    }
2710
2711    #[test]
2712    fn test_mixed_container_8() {
2713        let data = br#"brittany_area = { #5
2714            color = { 118  99  151 }
2715            169
2716        }"#;
2717
2718        assert_eq!(
2719            parse(&data[..]).unwrap().token_tape,
2720            vec![
2721                TextToken::Unquoted(Scalar::new(b"brittany_area")),
2722                TextToken::Object {
2723                    end: 10,
2724                    mixed: true
2725                },
2726                TextToken::Unquoted(Scalar::new(b"color")),
2727                TextToken::Array {
2728                    end: 7,
2729                    mixed: false
2730                },
2731                TextToken::Unquoted(Scalar::new(b"118")),
2732                TextToken::Unquoted(Scalar::new(b"99")),
2733                TextToken::Unquoted(Scalar::new(b"151")),
2734                TextToken::End(3),
2735                TextToken::MixedContainer,
2736                TextToken::Unquoted(Scalar::new(b"169")),
2737                TextToken::End(1),
2738            ]
2739        );
2740    }
2741
2742    #[test]
2743    fn test_mixed_container_9() {
2744        let data = b"a {{b=c d c}}";
2745        assert_eq!(
2746            parse(&data[..]).unwrap().token_tape,
2747            vec![
2748                TextToken::Unquoted(Scalar::new(b"a")),
2749                TextToken::Array {
2750                    end: 9,
2751                    mixed: false
2752                },
2753                TextToken::Object {
2754                    end: 8,
2755                    mixed: true
2756                },
2757                TextToken::Unquoted(Scalar::new(b"b")),
2758                TextToken::Unquoted(Scalar::new(b"c")),
2759                TextToken::MixedContainer,
2760                TextToken::Unquoted(Scalar::new(b"d")),
2761                TextToken::Unquoted(Scalar::new(b"c")),
2762                TextToken::End(2),
2763                TextToken::End(1)
2764            ]
2765        );
2766    }
2767
2768    #[test]
2769    fn test_mixed_container_10() {
2770        let data = br"on_actions = {
2771            faith_holy_order_land_acquisition_pulse
2772            delay = { days = { 5 10 }}
2773            faith_heresy_events_pulse
2774            delay = { days = { 15 20 }}
2775            faith_fervor_events_pulse
2776          }";
2777        assert_eq!(
2778            parse(&data[..]).unwrap().token_tape,
2779            vec![
2780                TextToken::Unquoted(Scalar::new(b"on_actions")),
2781                TextToken::Array {
2782                    end: 24,
2783                    mixed: true
2784                },
2785                TextToken::Unquoted(Scalar::new(b"faith_holy_order_land_acquisition_pulse")),
2786                TextToken::MixedContainer,
2787                TextToken::Unquoted(Scalar::new(b"delay")),
2788                TextToken::Operator(Operator::Equal),
2789                TextToken::Object {
2790                    end: 12,
2791                    mixed: false
2792                },
2793                TextToken::Unquoted(Scalar::new(b"days")),
2794                TextToken::Array {
2795                    end: 11,
2796                    mixed: false
2797                },
2798                TextToken::Unquoted(Scalar::new(b"5")),
2799                TextToken::Unquoted(Scalar::new(b"10")),
2800                TextToken::End(8),
2801                TextToken::End(6),
2802                TextToken::Unquoted(Scalar::new(b"faith_heresy_events_pulse")),
2803                TextToken::Unquoted(Scalar::new(b"delay")),
2804                TextToken::Operator(Operator::Equal),
2805                TextToken::Object {
2806                    end: 22,
2807                    mixed: false
2808                },
2809                TextToken::Unquoted(Scalar::new(b"days")),
2810                TextToken::Array {
2811                    end: 21,
2812                    mixed: false
2813                },
2814                TextToken::Unquoted(Scalar::new(b"15")),
2815                TextToken::Unquoted(Scalar::new(b"20")),
2816                TextToken::End(18),
2817                TextToken::End(16),
2818                TextToken::Unquoted(Scalar::new(b"faith_fervor_events_pulse")),
2819                TextToken::End(1)
2820            ]
2821        );
2822    }
2823
2824    #[test]
2825    fn test_dont_fail_on_list_keyword() {
2826        let data = br"  simple_cross_flag = {
2827            pattern = list christian_emblems_list
2828            color1 = list normal_colors
2829          }";
2830
2831        assert_eq!(
2832            parse(&data[..]).unwrap().token_tape,
2833            vec![
2834                TextToken::Unquoted(Scalar::new(b"simple_cross_flag")),
2835                TextToken::Object {
2836                    end: 10,
2837                    mixed: true
2838                },
2839                TextToken::Unquoted(Scalar::new(b"pattern")),
2840                TextToken::Unquoted(Scalar::new(b"list")),
2841                TextToken::MixedContainer,
2842                TextToken::Unquoted(Scalar::new(b"christian_emblems_list")),
2843                TextToken::Unquoted(Scalar::new(b"color1")),
2844                TextToken::Operator(Operator::Equal),
2845                TextToken::Unquoted(Scalar::new(b"list")),
2846                TextToken::Unquoted(Scalar::new(b"normal_colors")),
2847                TextToken::End(1)
2848            ]
2849        );
2850    }
2851
2852    #[test]
2853    fn test_parameter_eof() {
2854        let data = b"[[";
2855        TextTape::from_slice(data).unwrap_err();
2856    }
2857
2858    #[test]
2859    fn incomplete_object_fail_to_parse() {
2860        let data = b"T&}";
2861        TextTape::from_slice(data).unwrap_err();
2862    }
2863
2864    #[test]
2865    fn test_initial_end_does_not_panic() {
2866        let res = parse(&b"}"[..]);
2867        assert!(res.is_ok() || res.is_err());
2868    }
2869
2870    #[test]
2871    fn test_object_template_syntax() {
2872        let data = br"obj={
2873            { a = b }={ 1 2 3 }
2874    }";
2875
2876        assert_eq!(
2877            parse(&data[..]).unwrap().token_tape,
2878            vec![
2879                TextToken::Unquoted(Scalar::new(b"obj")),
2880                TextToken::Array {
2881                    end: 11,
2882                    mixed: false
2883                },
2884                TextToken::Object {
2885                    end: 5,
2886                    mixed: false
2887                },
2888                TextToken::Unquoted(Scalar::new(b"a")),
2889                TextToken::Unquoted(Scalar::new(b"b")),
2890                TextToken::End(2),
2891                TextToken::Array {
2892                    end: 10,
2893                    mixed: false
2894                },
2895                TextToken::Unquoted(Scalar::new(b"1")),
2896                TextToken::Unquoted(Scalar::new(b"2")),
2897                TextToken::Unquoted(Scalar::new(b"3")),
2898                TextToken::End(6),
2899                TextToken::End(1)
2900            ]
2901        );
2902    }
2903
2904    #[test]
2905    fn test_object_template_syntax2() {
2906        let data = br"obj={
2907            { foo }={ 1000 }
2908    }";
2909
2910        assert_eq!(
2911            parse(&data[..]).unwrap().token_tape,
2912            vec![
2913                TextToken::Unquoted(Scalar::new(b"obj")),
2914                TextToken::Array {
2915                    end: 8,
2916                    mixed: false
2917                },
2918                TextToken::Array {
2919                    end: 4,
2920                    mixed: false
2921                },
2922                TextToken::Unquoted(Scalar::new(b"foo")),
2923                TextToken::End(2),
2924                TextToken::Array {
2925                    end: 7,
2926                    mixed: false
2927                },
2928                TextToken::Unquoted(Scalar::new(b"1000")),
2929                TextToken::End(5),
2930                TextToken::End(1)
2931            ]
2932        );
2933    }
2934
2935    #[test]
2936    fn test_object_template_syntax_simple_scalar() {
2937        let data = br"obj={ { a=b }=16 }";
2938        let tape = TextTape::from_slice(data).unwrap();
2939        let tokens = &tape.tokens();
2940
2941        assert_eq!(
2942            tokens,
2943            &[
2944                TextToken::Unquoted(Scalar::new(b"obj")),
2945                TextToken::Array {
2946                    end: 7,
2947                    mixed: false
2948                },
2949                TextToken::Object {
2950                    end: 5,
2951                    mixed: false
2952                },
2953                TextToken::Unquoted(Scalar::new(b"a")),
2954                TextToken::Unquoted(Scalar::new(b"b")),
2955                TextToken::End(2),
2956                TextToken::Unquoted(Scalar::new(b"16")),
2957                TextToken::End(1),
2958            ]
2959        );
2960    }
2961
2962    #[test]
2963    fn test_object_template_syntax_scalar_value() {
2964        let data = br"obj={ { 31=16 }=16 { 32=17 }=18 }";
2965        assert_eq!(
2966            parse(&data[..]).unwrap().token_tape,
2967            vec![
2968                TextToken::Unquoted(Scalar::new(b"obj")),
2969                TextToken::Array {
2970                    end: 12,
2971                    mixed: false
2972                },
2973                TextToken::Object {
2974                    end: 5,
2975                    mixed: false
2976                },
2977                TextToken::Unquoted(Scalar::new(b"31")),
2978                TextToken::Unquoted(Scalar::new(b"16")),
2979                TextToken::End(2),
2980                TextToken::Unquoted(Scalar::new(b"16")),
2981                TextToken::Object {
2982                    end: 10,
2983                    mixed: false
2984                },
2985                TextToken::Unquoted(Scalar::new(b"32")),
2986                TextToken::Unquoted(Scalar::new(b"17")),
2987                TextToken::End(7),
2988                TextToken::Unquoted(Scalar::new(b"18")),
2989                TextToken::End(1)
2990            ]
2991        );
2992    }
2993
2994    #[test]
2995    fn test_semicolon_as_whitespace() {
2996        let data = b"foo = 0.3;";
2997        let tape = TextTape::from_slice(&data[..]).unwrap();
2998        assert_eq!(
2999            tape.tokens(),
3000            vec![
3001                TextToken::Unquoted(Scalar::new(b"foo")),
3002                TextToken::Unquoted(Scalar::new(b"0.3")),
3003            ]
3004        );
3005    }
3006
3007    #[test]
3008    fn test_multiple_semicolons_as_whitespace() {
3009        let data = b"a = 1; b = 2;; c = 3;";
3010        let tape = TextTape::from_slice(&data[..]).unwrap();
3011        assert_eq!(
3012            tape.tokens(),
3013            vec![
3014                TextToken::Unquoted(Scalar::new(b"a")),
3015                TextToken::Unquoted(Scalar::new(b"1")),
3016                TextToken::Unquoted(Scalar::new(b"b")),
3017                TextToken::Unquoted(Scalar::new(b"2")),
3018                TextToken::Unquoted(Scalar::new(b"c")),
3019                TextToken::Unquoted(Scalar::new(b"3")),
3020            ]
3021        );
3022    }
3023}