fundu_core/
parse.rs

1// Copyright (c) 2023 Joining7943 <joining@posteo.de>
2//
3// This software is released under the MIT License.
4// https://opensource.org/licenses/MIT
5
6//! This module is the working horse of the parser. Public interfaces to the parser are located in
7//! the main library `lib.rs`.
8
9use std::cmp::Ordering::{Equal, Greater, Less};
10use std::str::Utf8Error;
11use std::time::Duration as StdDuration;
12
13use crate::config::{Config, Delimiter, NumbersLike, DEFAULT_CONFIG};
14use crate::error::ParseError;
15use crate::time::{Duration, Multiplier, TimeUnit, TimeUnitsLike};
16use crate::util::POW10;
17
18pub const ATTOS_PER_SEC: u64 = 1_000_000_000_000_000_000;
19pub const ATTOS_PER_SEC_U128: u128 = ATTOS_PER_SEC as u128;
20pub const ATTOS_PER_NANO: u64 = 1_000_000_000;
21pub const ATTOS_PER_NANO_U128: u128 = ATTOS_PER_NANO as u128;
22pub const NANOS_PER_SEC: u64 = 1_000_000_000;
23pub const NANOS_PER_SEC_U128: u128 = NANOS_PER_SEC as u128;
24pub const ATTOS_MAX: u64 = 999_999_999_999_999_999;
25pub const SECONDS_MAX: u64 = u64::MAX;
26pub const SECONDS_AND_ATTOS_MAX: (u64, u64) = (SECONDS_MAX, ATTOS_MAX);
27
28/// The core duration parser to parse strings into a [`crate::time::Duration`]
29///
30/// To be able to use the [`Parser::parse`] method an implementation of the
31/// [`crate::time::TimeUnitsLike`] trait is needed for the time units (even if there are no time
32/// units) and optionally for time keywords (like `yesterday` and `tomorrow` etc.). Optionally, an
33/// implementation of the [`NumbersLike`] trait can be provided, too. The `custom` and `standard`
34/// features have such implementations and their parsers are more convenient to use than using this
35/// parser directly. However, for example, the `custom` feature's [`fundu::CustomDurationParser`]
36/// cannot be fully built in `const` context and is a slightly slower than this parser. So, using
37/// this parser is more involved but if maximum performance and building a parser in `const` context
38/// is wanted then this parser is the better choice.
39///
40/// # Examples
41///
42/// ```rust
43/// use fundu_core::error::ParseError;
44/// use fundu_core::parse::Parser;
45/// use fundu_core::time::TimeUnit::*;
46/// use fundu_core::time::{Duration, Multiplier, TimeUnit, TimeUnitsLike};
47///
48/// struct TimeUnits {}
49///
50/// impl TimeUnitsLike for TimeUnits {
51///     #[inline]
52///     fn is_empty(&self) -> bool {
53///         false
54///     }
55///
56///     #[inline]
57///     fn get(&self, identifier: &str) -> Option<(TimeUnit, Multiplier)> {
58///         match identifier {
59///             "s" | "sec" | "secs" => Some((Second, Multiplier(1, 0))),
60///             "m" | "min" | "mins" => Some((Minute, Multiplier(1, 0))),
61///             _ => None,
62///         }
63///     }
64/// }
65///
66/// let parser = Parser::new();
67/// let time_units = TimeUnits {};
68///
69/// assert_eq!(
70///     parser.parse("1.0s", &time_units, None, None),
71///     Ok(Duration::positive(1, 0))
72/// );
73/// assert_eq!(
74///     parser.parse("1min", &time_units, None, None),
75///     Ok(Duration::positive(60, 0))
76/// );
77/// assert_eq!(
78///     parser.parse("1ms", &time_units, None, None),
79///     Err(ParseError::TimeUnit(
80///         1,
81///         "Invalid time unit: 'ms'".to_string()
82///     ))
83/// );
84/// ```
85///
86/// [`fundu::CustomDurationParser`]: https://docs.rs/fundu/latest/fundu/struct.CustomDurationParser.html
87#[derive(Debug, PartialEq, Eq)]
88pub struct Parser<'a> {
89    /// The [`crate::config::Config`] of this [`Parser`]
90    ///
91    /// For convenience, there are also the const [`Parser::new`] and const [`Parser::with_config`]
92    /// methods to create a new [`Parser`].
93    pub config: Config<'a>,
94}
95
96impl<'a> Parser<'a> {
97    /// Convenience method to create a new parser with the default [`crate::config::Config`]
98    pub const fn new() -> Self {
99        Self {
100            config: DEFAULT_CONFIG,
101        }
102    }
103
104    /// Convenience method to create a new parser with the the given [`crate::config::Config`]
105    pub const fn with_config(config: Config<'a>) -> Self {
106        Self { config }
107    }
108
109    #[inline]
110    pub fn parse_multiple(
111        &self,
112        source: &str,
113        time_units: &dyn TimeUnitsLike,
114        keywords: Option<&dyn TimeUnitsLike>,
115        numerals: Option<&dyn NumbersLike>,
116    ) -> Result<Duration, ParseError> {
117        let mut duration = Duration::ZERO;
118
119        let mut parser = &mut ReprParserMultiple::new(source);
120        loop {
121            let (mut duration_repr, maybe_parser) =
122                parser.parse(&self.config, time_units, keywords, numerals)?;
123            let parsed_duration = duration_repr.parse()?;
124            duration = if !self.config.allow_negative && parsed_duration.is_negative() {
125                return Err(ParseError::NegativeNumber);
126            } else if parsed_duration.is_zero() {
127                duration
128            } else if duration.is_zero() {
129                parsed_duration
130            } else {
131                duration.saturating_add(parsed_duration)
132            };
133            match maybe_parser {
134                Some(p) => parser = p,
135                None => break Ok(duration),
136            }
137        }
138    }
139
140    #[inline]
141    pub fn parse_single(
142        &self,
143        source: &str,
144        time_units: &dyn TimeUnitsLike,
145        keywords: Option<&dyn TimeUnitsLike>,
146        numerals: Option<&dyn NumbersLike>,
147    ) -> Result<Duration, ParseError> {
148        ReprParserSingle::new(source)
149            .parse(&self.config, time_units, keywords, numerals)
150            .and_then(|mut duration_repr| {
151                duration_repr.parse().and_then(|duration| {
152                    if !self.config.allow_negative && duration.is_negative() {
153                        Err(ParseError::NegativeNumber)
154                    } else {
155                        Ok(duration)
156                    }
157                })
158            })
159    }
160
161    /// Parse the `source` string into a saturating [`crate::time::Duration`]
162    ///
163    /// This method needs a struct implementing the [`crate::time::TimeUnitsLike`] for time units
164    /// and optionally for time keywords (like `yesterday`, `tomorrow`). But also [`NumbersLike`]
165    /// implementations for words like `one`, `next`, `last` are supported. The `standard` and
166    /// `custom` features of `fundu`  offer such implementations and are more convenient to use than
167    /// using this method directly. They both provide facades and an own parser which calls this
168    /// method.
169    ///
170    /// # Errors
171    ///
172    /// Returns a [`crate::error::ParseError`] if the given `source` string is invalid
173    ///
174    /// # Examples
175    ///
176    /// An example with a quick and dirty implementation of [`crate::time::TimeUnitsLike`]
177    ///
178    /// ```rust
179    /// use fundu_core::error::ParseError;
180    /// use fundu_core::parse::Parser;
181    /// use fundu_core::time::TimeUnit::*;
182    /// use fundu_core::time::{Duration, Multiplier, TimeUnit, TimeUnitsLike};
183    ///
184    /// struct TimeUnits {}
185    ///
186    /// impl TimeUnitsLike for TimeUnits {
187    ///     #[inline]
188    ///     fn is_empty(&self) -> bool {
189    ///         false
190    ///     }
191    ///
192    ///     #[inline]
193    ///     fn get(&self, identifier: &str) -> Option<(TimeUnit, Multiplier)> {
194    ///         match identifier {
195    ///             "s" | "sec" | "secs" => Some((Second, Multiplier(1, 0))),
196    ///             "m" | "min" | "mins" => Some((Minute, Multiplier(1, 0))),
197    ///             _ => None,
198    ///         }
199    ///     }
200    /// }
201    ///
202    /// let parser = Parser::new();
203    /// let time_units = TimeUnits {};
204    ///
205    /// assert_eq!(
206    ///     parser.parse("1.0s", &time_units, None, None),
207    ///     Ok(Duration::positive(1, 0))
208    /// );
209    /// assert_eq!(
210    ///     parser.parse("1min", &time_units, None, None),
211    ///     Ok(Duration::positive(60, 0))
212    /// );
213    /// assert_eq!(
214    ///     parser.parse("1ms", &time_units, None, None),
215    ///     Err(ParseError::TimeUnit(
216    ///         1,
217    ///         "Invalid time unit: 'ms'".to_string()
218    ///     ))
219    /// );
220    /// ```
221    #[inline]
222    pub fn parse(
223        &self,
224        source: &str,
225        time_units: &dyn TimeUnitsLike,
226        keywords: Option<&dyn TimeUnitsLike>,
227        numerals: Option<&dyn NumbersLike>,
228    ) -> Result<Duration, ParseError> {
229        if self.config.allow_multiple {
230            self.parse_multiple(source, time_units, keywords, numerals)
231        } else {
232            self.parse_single(source, time_units, keywords, numerals)
233        }
234    }
235}
236
237impl<'a> Default for Parser<'a> {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243pub trait Parse8Digits {
244    // This method is based on the work of Johnny Lee and his blog post
245    // https://johnnylee-sde.github.io/Fast-numeric-string-to-int
246    unsafe fn parse_8_digits(digits: &[u8]) -> u64 {
247        // cov:excl-start
248        debug_assert!(
249            digits.len() >= 8,
250            "Call this method only if digits has length >= 8"
251        ); // cov:excl-stop
252
253        // This cast to a more strictly aligned type is safe since we're using
254        // ptr.read_unaligned
255        #[allow(clippy::cast_ptr_alignment)]
256        let ptr = digits.as_ptr().cast::<u64>();
257        let mut num = u64::from_le(ptr.read_unaligned());
258        num = ((num & 0x0F0F_0F0F_0F0F_0F0F).wrapping_mul(2561)) >> 8i32;
259        num = ((num & 0x00FF_00FF_00FF_00FF).wrapping_mul(6_553_601)) >> 16i32;
260        num = ((num & 0x0000_FFFF_0000_FFFF).wrapping_mul(42_949_672_960_001)) >> 32i32;
261        num
262    }
263}
264
265#[derive(Debug, PartialEq, Eq, Default, Copy, Clone)]
266pub struct Whole(pub usize, pub usize);
267
268impl Parse8Digits for Whole {}
269
270impl Whole {
271    pub fn parse_slice(mut seconds: u64, digits: &[u8]) -> Option<u64> {
272        if digits.len() >= 8 {
273            let mut iter = digits.chunks_exact(8);
274            for digits in iter.by_ref() {
275                match seconds
276                    .checked_mul(100_000_000)
277                    // SAFETY: We have chunks of exactly 8 bytes
278                    .and_then(|s| s.checked_add(unsafe { Self::parse_8_digits(digits) }))
279                {
280                    Some(s) => seconds = s,
281                    None => {
282                        return None;
283                    }
284                }
285            }
286            for num in iter.remainder() {
287                match seconds
288                    .checked_mul(10)
289                    .and_then(|s| s.checked_add(u64::from(*num - b'0')))
290                {
291                    Some(s) => seconds = s,
292                    None => {
293                        return None;
294                    }
295                }
296            }
297        } else {
298            for num in digits {
299                match seconds
300                    .checked_mul(10)
301                    .and_then(|s| s.checked_add(u64::from(*num - b'0')))
302                {
303                    Some(s) => seconds = s,
304                    None => {
305                        return None;
306                    }
307                }
308            }
309        }
310        Some(seconds)
311    }
312
313    pub fn parse(digits: &[u8], append: Option<&[u8]>, zeros: Option<usize>) -> Option<u64> {
314        if digits.is_empty() && append.map_or(true, <[u8]>::is_empty) {
315            return Some(0);
316        }
317
318        Self::parse_slice(0, digits).and_then(|s| {
319            append
320                .map_or(Some(s), |append| Self::parse_slice(s, append))
321                .and_then(|seconds| {
322                    if seconds == 0 {
323                        Some(0)
324                    } else {
325                        match zeros {
326                            Some(num_zeros) if num_zeros > 0 => POW10
327                                .get(num_zeros)
328                                .and_then(|pow| seconds.checked_mul(*pow)),
329                            Some(_) | None => Some(seconds),
330                        }
331                    }
332                })
333        })
334    }
335
336    #[inline]
337    pub const fn len(&self) -> usize {
338        self.1 - self.0
339    }
340
341    #[inline]
342    pub const fn is_empty(&self) -> bool {
343        self.1 == self.0
344    }
345}
346
347#[derive(Debug, PartialEq, Eq, Default, Copy, Clone)]
348pub struct Fract(pub usize, pub usize);
349
350impl Parse8Digits for Fract {}
351
352impl Fract {
353    pub fn parse_slice(mut attos: u64, max_to_parse: usize, digits: &[u8]) -> (u64, usize) {
354        let num_parsable = digits.len().min(max_to_parse);
355        if num_parsable >= 8 {
356            let mut iter = digits[..num_parsable].chunks_exact(8);
357            for digits in iter.by_ref() {
358                // SAFETY: We have chunks of exactly 8 bytes
359                attos = attos * 100_000_000 + unsafe { Self::parse_8_digits(digits) };
360            }
361            for num in iter.remainder() {
362                attos = attos * 10 + u64::from(*num - b'0');
363            }
364        } else {
365            for num in &digits[..num_parsable] {
366                attos = attos * 10 + u64::from(*num - b'0');
367            }
368        }
369        (attos, max_to_parse - num_parsable)
370    }
371
372    pub fn parse(digits: &[u8], prepend: Option<&[u8]>, zeros: Option<usize>) -> u64 {
373        if digits.is_empty() && prepend.map_or(true, <[u8]>::is_empty) {
374            return 0;
375        }
376
377        let max_to_parse = match zeros {
378            Some(z) if z > 18 => return 0,
379            Some(z) => 18 - z,
380            None => 18,
381        };
382
383        match prepend {
384            Some(prepend) if !prepend.is_empty() => {
385                let (attos, remainder) = Self::parse_slice(0, max_to_parse, prepend);
386                if remainder == 0 {
387                    attos
388                } else if digits.is_empty() {
389                    attos * POW10[remainder]
390                } else {
391                    let (attos, remainder) = Self::parse_slice(attos, remainder, digits);
392                    if remainder > 0 {
393                        attos * POW10[remainder]
394                    } else {
395                        attos
396                    }
397                }
398            }
399            Some(_) | None => {
400                let (attos, remainder) = Self::parse_slice(0, max_to_parse, digits);
401                if remainder > 0 {
402                    attos * POW10[remainder]
403                } else {
404                    attos
405                }
406            }
407        }
408    }
409
410    #[inline]
411    pub const fn len(&self) -> usize {
412        self.1 - self.0
413    }
414
415    #[inline]
416    pub const fn is_empty(&self) -> bool {
417        self.1 == self.0
418    }
419}
420
421#[derive(Debug, Default)]
422pub struct DurationRepr<'a> {
423    pub default_unit: TimeUnit,
424    pub unit: Option<TimeUnit>,
425    pub is_negative: Option<bool>,
426    pub is_infinite: bool,
427    pub whole: Option<Whole>,
428    pub fract: Option<Fract>,
429    pub input: &'a [u8],
430    pub exponent: i16,
431    pub multiplier: Multiplier,
432    pub numeral: Option<Multiplier>,
433}
434
435impl<'a> DurationRepr<'a> {
436    #[allow(clippy::too_many_lines)]
437    pub fn parse(&mut self) -> Result<Duration, ParseError> {
438        if self.is_infinite {
439            return Ok(Duration::from_std(
440                self.is_negative.unwrap_or_default(),
441                StdDuration::MAX,
442            ));
443        }
444
445        if self.whole.is_none() && self.fract.is_none() {
446            return if self.numeral.is_some() {
447                let time_unit = self.unit.expect("Numeral without time unit");
448                let numeral = self.numeral.unwrap();
449                let Multiplier(coefficient, exponent) =
450                    numeral * time_unit.multiplier() * self.multiplier;
451
452                Ok(self.parse_duration_with_fixed_number(coefficient, exponent))
453            // We're here when parsing keywords or time units without a number if the configuration
454            // option `number_is_optional` is set.
455            } else if self.unit.is_some() {
456                let time_unit = self.unit.unwrap_or(self.default_unit);
457                let Multiplier(coefficient, exponent) = time_unit.multiplier() * self.multiplier;
458
459                Ok(self.parse_duration_with_fixed_number(coefficient, exponent))
460            // We're here if we wouldn't have parsed anything usable.
461            } else {
462                unreachable!() // cov:excl-line
463            };
464        }
465
466        let time_unit = self.unit.unwrap_or(self.default_unit);
467        // Panic on overflow during the multiplication of the multipliers or adding the exponents
468        let Multiplier(coefficient, exponent) = time_unit.multiplier() * self.multiplier;
469        if coefficient == 0 {
470            return Ok(Duration::ZERO);
471        }
472        let exponent = i32::from(exponent) + i32::from(self.exponent);
473
474        // The maximum absolute value of the exponent is `2 * abs(i16::MIN)`, so it is safe to cast
475        // to usize
476        let exponent_abs: usize = exponent.unsigned_abs().try_into().unwrap();
477        let duration_is_negative = self.is_negative.unwrap_or_default() ^ coefficient.is_negative();
478
479        // We're operating on slices to minimize runtime costs. Applying the exponent before parsing
480        // to integers is necessary, since the exponent can move digits into the to be considered
481        // final integer domain.
482        let digits = self.input;
483        let (seconds, attos) = match (exponent.cmp(&0i32), &self.whole, &self.fract) {
484            (Less, Some(whole), fract) if whole.len() > exponent_abs => {
485                match Whole::parse(&digits[whole.0..whole.1 - exponent_abs], None, None) {
486                    Some(seconds) => {
487                        let attos = Fract::parse(
488                            fract.map_or_else(|| [].as_ref(), |fract| &digits[fract.0..fract.1]),
489                            Some(&digits[whole.1 - exponent_abs..whole.1]),
490                            None,
491                        );
492                        (seconds, attos)
493                    }
494                    None if duration_is_negative => return Ok(Duration::MIN),
495                    None => return Ok(Duration::MAX),
496                }
497            }
498            (Less, whole, fract) => {
499                let attos = match fract {
500                    Some(fract) if fract.is_empty() => Fract::parse(
501                        whole.map_or_else(|| [].as_ref(), |whole| &digits[whole.0..whole.1]),
502                        None,
503                        Some(exponent_abs - whole.map_or(0, |w| w.len())),
504                    ),
505                    Some(fract) => Fract::parse(
506                        &digits[fract.0..fract.1],
507                        whole.and_then(|whole| {
508                            (!whole.is_empty()).then(|| &digits[whole.0..whole.1])
509                        }),
510                        Some(exponent_abs - whole.map_or(0, |w| w.len())),
511                    ),
512                    None => Fract::parse(
513                        whole.map_or_else(|| [].as_ref(), |whole| &digits[whole.0..whole.1]),
514                        None,
515                        Some(exponent_abs - whole.map_or(0, |w| w.len())),
516                    ),
517                };
518                (0, attos)
519            }
520            (Equal, whole, fract) => {
521                match whole.map_or(Some(0), |whole| {
522                    Whole::parse(&digits[whole.0..whole.1], None, None)
523                }) {
524                    Some(seconds) => {
525                        let attos = fract.map_or(0, |fract| {
526                            Fract::parse(&digits[fract.0..fract.1], None, None)
527                        });
528                        (seconds, attos)
529                    }
530                    None if duration_is_negative => return Ok(Duration::MIN),
531                    None => return Ok(Duration::MAX),
532                }
533            }
534            (Greater, whole, Some(fract)) if fract.len() > exponent_abs => {
535                match Whole::parse(
536                    whole.map_or_else(|| [].as_ref(), |whole| &digits[whole.0..whole.1]),
537                    Some(&digits[fract.0..fract.0 + exponent_abs]),
538                    None,
539                ) {
540                    Some(seconds) => {
541                        let attos =
542                            Fract::parse(&digits[fract.0 + exponent_abs..fract.1], None, None);
543                        (seconds, attos)
544                    }
545                    None if duration_is_negative => return Ok(Duration::MIN),
546                    None => return Ok(Duration::MAX),
547                }
548            }
549            (Greater, whole, fract) => {
550                match Whole::parse(
551                    whole.map_or_else(|| [].as_ref(), |whole| &digits[whole.0..whole.1]),
552                    fract.map(|fract| &digits[fract.0..fract.1]),
553                    Some(exponent_abs - fract.map_or(0, |fract| fract.len())),
554                ) {
555                    Some(seconds) => (seconds, 0),
556                    None if duration_is_negative => return Ok(Duration::MIN),
557                    None => return Ok(Duration::MAX),
558                }
559            }
560        };
561
562        Ok(Self::calculate_duration(
563            duration_is_negative,
564            seconds,
565            attos,
566            coefficient,
567        ))
568    }
569
570    #[inline]
571    pub fn parse_duration_with_fixed_number(&self, coefficient: i64, exponent: i16) -> Duration {
572        if coefficient == 0 {
573            return Duration::ZERO;
574        }
575        let duration_is_negative = coefficient.is_negative() ^ self.is_negative.unwrap_or_default();
576        let (seconds, attos) = match exponent.cmp(&0i16) {
577            Less if exponent < -18 => return Duration::ZERO,
578            Less => (0, POW10[usize::try_from(18 + exponent).unwrap()]),
579            Equal => {
580                return Duration::from_std(
581                    duration_is_negative,
582                    StdDuration::new(coefficient.unsigned_abs(), 0),
583                );
584            }
585            Greater if exponent > 19 => {
586                return if coefficient.is_negative() {
587                    Duration::MIN
588                } else {
589                    Duration::MAX
590                };
591            }
592            Greater => (POW10[usize::try_from(exponent).unwrap()], 0),
593        };
594
595        Self::calculate_duration(duration_is_negative, seconds, attos, coefficient)
596    }
597
598    #[inline]
599    pub fn calculate_duration(
600        is_negative: bool,
601        seconds: u64,
602        attos: u64,
603        coefficient: i64,
604    ) -> Duration {
605        if (seconds == 0 && attos == 0) || coefficient == 0 {
606            Duration::ZERO
607        } else if attos == 0 {
608            let unsigned_coefficient = coefficient.unsigned_abs();
609            match seconds.checked_mul(unsigned_coefficient) {
610                Some(s) => Duration::from_std(is_negative, StdDuration::new(s, 0)),
611                None if is_negative => Duration::MIN,
612                None => Duration::MAX,
613            }
614        } else if coefficient == 1 || coefficient == -1 {
615            Duration::from_std(
616                is_negative,
617                StdDuration::new(seconds, (attos / ATTOS_PER_NANO).try_into().unwrap()),
618            )
619        } else {
620            let unsigned_coefficient = coefficient.unsigned_abs();
621            if let Some(attos) = unsigned_coefficient.checked_mul(attos) {
622                match seconds
623                    .checked_mul(unsigned_coefficient)
624                    .and_then(|s| s.checked_add(attos / ATTOS_PER_SEC))
625                {
626                    Some(s) => Duration::from_std(
627                        is_negative,
628                        StdDuration::new(s, ((attos / ATTOS_PER_NANO) % NANOS_PER_SEC) as u32),
629                    ),
630                    None if is_negative => Duration::MIN,
631                    None => Duration::MAX,
632                }
633            } else {
634                let time = seconds.checked_mul(unsigned_coefficient).and_then(|s| {
635                    let attos = u128::from(attos) * u128::from(unsigned_coefficient);
636                    s.checked_add((attos / ATTOS_PER_SEC_U128).try_into().unwrap())
637                        .map(|s| (s, attos))
638                });
639                match time {
640                    Some((s, attos)) => Duration::from_std(
641                        is_negative,
642                        StdDuration::new(
643                            s,
644                            ((attos / ATTOS_PER_NANO_U128) % NANOS_PER_SEC_U128) as u32,
645                        ),
646                    ),
647                    None if is_negative => Duration::MIN,
648                    None => Duration::MAX,
649                }
650            }
651        }
652    }
653}
654
655pub struct BytesRange(usize, usize);
656
657pub struct Bytes<'a> {
658    pub current_pos: usize, // keep first. Has better performance.
659    pub current_byte: Option<&'a u8>,
660    pub buffer: Option<(usize, &'a [u8], Option<&'a u8>)>,
661    pub input: &'a [u8],
662}
663
664impl<'a> Bytes<'a> {
665    #[inline]
666    pub const fn new(input: &'a [u8]) -> Self {
667        Self {
668            current_pos: 0,
669            current_byte: input.first(),
670            input,
671            buffer: None,
672        }
673    }
674
675    #[inline]
676    pub fn advance(&mut self) {
677        self.current_pos += 1;
678        self.current_byte = self.input.get(self.current_pos);
679    }
680
681    #[inline]
682    pub unsafe fn advance_by(&mut self, num: usize) {
683        self.current_pos += num;
684        self.current_byte = self.input.get(self.current_pos);
685    }
686
687    pub fn advance_to<F>(&mut self, delimiter: F) -> &'a [u8]
688    where
689        F: Fn(u8) -> bool,
690    {
691        let start = self.current_pos;
692        while let Some(byte) = self.current_byte {
693            if delimiter(*byte) {
694                break;
695            }
696            self.advance();
697        }
698        &self.input[start..self.current_pos]
699    }
700
701    pub fn buffered_advance_to<F>(&mut self, delimiter: F) -> &'a [u8]
702    where
703        F: Fn(u8) -> bool,
704    {
705        match self.buffer {
706            Some((pos, buffer, Some(next))) if pos == self.current_pos && delimiter(*next) => {
707                // SAFETY: The buffer length does not exceed the input because we parsed it before
708                // at the same position
709                unsafe { self.advance_by(buffer.len()) };
710                buffer
711            }
712            Some((pos, buffer, None)) if pos == self.current_pos => {
713                // SAFETY: The buffer length does not exceed the input because we parsed it before
714                // at the same position
715                unsafe { self.advance_by(buffer.len()) };
716                buffer
717            }
718            None | Some(_) => {
719                let start = self.current_pos;
720                while let Some(byte) = self.current_byte {
721                    if delimiter(*byte) {
722                        break;
723                    }
724                    self.advance();
725                }
726                let buffer = &self.input[start..self.current_pos];
727                self.buffer = Some((start, buffer, self.current_byte));
728                buffer
729            }
730        }
731    }
732
733    #[inline]
734    pub fn peek(&self, num: usize) -> Option<&[u8]> {
735        self.input.get(self.current_pos..self.current_pos + num)
736    }
737
738    #[inline]
739    pub fn get_remainder(&self) -> &[u8] {
740        &self.input[self.current_pos..]
741    }
742
743    #[inline]
744    pub unsafe fn get_remainder_str_unchecked(&self) -> &str {
745        std::str::from_utf8_unchecked(self.get_remainder())
746    }
747
748    #[inline]
749    pub fn get_remainder_str(&self) -> Result<&str, ParseError> {
750        std::str::from_utf8(self.get_remainder())
751            .map_err(|err| ParseError::InvalidInput(err.to_string()))
752    }
753
754    #[inline]
755    pub fn get_current_str(&self, start: usize) -> Result<&str, Utf8Error> {
756        std::str::from_utf8(&self.input[start..self.current_pos])
757    }
758
759    #[inline]
760    pub fn finish(&mut self) {
761        self.current_pos = self.input.len();
762        self.current_byte = None;
763    }
764
765    #[inline]
766    pub fn reset(&mut self, position: usize) {
767        self.current_pos = position;
768        self.current_byte = self.input.get(position);
769    }
770
771    #[inline]
772    pub fn parse_digit(&mut self) -> Option<u8> {
773        self.current_byte.and_then(|byte| {
774            let digit = byte.wrapping_sub(b'0');
775            (digit < 10).then(|| {
776                self.advance();
777                *byte
778            })
779        })
780    }
781
782    pub fn parse_digits_strip_zeros(&mut self) -> BytesRange {
783        const ASCII_EIGHT_ZEROS: u64 = 0x3030_3030_3030_3030;
784
785        debug_assert!(self.current_byte.map_or(false, u8::is_ascii_digit)); // cov:excl-stop
786
787        let mut start = self.current_pos;
788        let mut strip_leading_zeros = true;
789        while let Some(eight) = self.parse_8_digits() {
790            if strip_leading_zeros {
791                if eight == ASCII_EIGHT_ZEROS {
792                    start += 8;
793                } else {
794                    strip_leading_zeros = false;
795
796                    // eight is little endian so we need to count the trailing zeros
797                    let leading_zeros = (eight - ASCII_EIGHT_ZEROS).trailing_zeros() / 8;
798                    start += leading_zeros as usize;
799                }
800            }
801        }
802
803        while let Some(byte) = self.current_byte {
804            let digit = byte.wrapping_sub(b'0');
805            if digit < 10 {
806                if strip_leading_zeros {
807                    if digit == 0 {
808                        start += 1;
809                    } else {
810                        strip_leading_zeros = false;
811                    }
812                }
813                self.advance();
814            } else {
815                break;
816            }
817        }
818
819        BytesRange(start, self.current_pos)
820    }
821
822    pub fn parse_digits(&mut self) -> BytesRange {
823        debug_assert!(self.current_byte.map_or(false, u8::is_ascii_digit)); // cov:excl-stop
824
825        let start = self.current_pos;
826        while self.parse_8_digits().is_some() {}
827        while self.parse_digit().is_some() {}
828
829        BytesRange(start, self.current_pos)
830    }
831
832    /// This method is based on the work of Daniel Lemire and his blog post
833    /// <https://lemire.me/blog/2018/09/30/quickly-identifying-a-sequence-of-digits-in-a-string-of-characters/>
834    pub fn parse_8_digits(&mut self) -> Option<u64> {
835        self.input
836            .get(self.current_pos..(self.current_pos + 8))
837            .and_then(|digits| {
838                // This cast to a more strictly aligned type is safe since we're using
839                // ptr.read_unaligned
840                #[allow(clippy::cast_ptr_alignment)]
841                let ptr = digits.as_ptr().cast::<u64>();
842                // SAFETY: We just ensured there are 8 bytes
843                let num = u64::from_le(unsafe { ptr.read_unaligned() });
844                ((num & (num.wrapping_add(0x0606_0606_0606_0606)) & 0xf0f0_f0f0_f0f0_f0f0)
845                    == 0x3030_3030_3030_3030)
846                    .then(|| {
847                        // SAFETY: We just ensured there are 8 bytes
848                        unsafe { self.advance_by(8) }
849                        num
850                    })
851            })
852    }
853
854    #[inline]
855    pub fn next_is_ignore_ascii_case(&self, word: &[u8]) -> bool {
856        self.peek(word.len())
857            .map_or(false, |bytes| bytes.eq_ignore_ascii_case(word))
858    }
859
860    #[inline]
861    pub const fn is_end_of_input(&self) -> bool {
862        self.current_byte.is_none()
863    }
864
865    #[inline]
866    pub fn check_end_of_input(&self) -> Result<(), ParseError> {
867        self.current_byte.map_or(Ok(()), |_| {
868            self.get_remainder_str().and_then(|remainder| {
869                Err(ParseError::Syntax(
870                    self.current_pos,
871                    format!("Expected end of input but found: '{remainder}'"),
872                ))
873            })
874        })
875    }
876
877    pub fn try_consume_delimiter(&mut self, delimiter: Delimiter) -> Result<(), ParseError> {
878        debug_assert!(delimiter(*self.current_byte.unwrap())); // cov:excl-line
879        if self.current_pos == 0 {
880            return Err(ParseError::Syntax(
881                0,
882                "Input may not start with a delimiter".to_owned(),
883            ));
884        }
885
886        let start = self.current_pos;
887        self.advance();
888        while let Some(byte) = self.current_byte {
889            if delimiter(*byte) {
890                self.advance();
891            } else {
892                break;
893            }
894        }
895
896        match self.current_byte {
897            Some(_) => Ok(()),
898            None => Err(ParseError::Syntax(
899                start,
900                "Input may not end with a delimiter".to_owned(),
901            )),
902        }
903    }
904}
905
906pub trait ReprParserTemplate<'a> {
907    type Output;
908
909    fn bytes(&mut self) -> &mut Bytes<'a>;
910
911    fn make_output(&'a mut self, duration_repr: DurationRepr<'a>) -> Self::Output;
912
913    fn parse_infinity_remainder(
914        &'a mut self,
915        duration_repr: DurationRepr<'a>,
916        config: &'a Config,
917    ) -> Result<Self::Output, ParseError>;
918
919    fn parse_keyword(
920        &mut self,
921        keywords: Option<&dyn TimeUnitsLike>,
922        config: &'a Config,
923    ) -> Result<Option<(TimeUnit, Multiplier)>, ParseError>;
924
925    fn parse_time_unit(
926        &mut self,
927        config: &'a Config,
928        time_units: &dyn TimeUnitsLike,
929    ) -> Result<Option<(TimeUnit, Multiplier)>, ParseError>;
930
931    fn parse_number_time_unit(
932        &mut self,
933        duration_repr: &mut DurationRepr<'a>,
934        config: &'a Config,
935        time_units: &dyn TimeUnitsLike,
936    ) -> Result<bool, ParseError>;
937
938    fn finalize(
939        &'a mut self,
940        duration_repr: DurationRepr<'a>,
941        config: &'a Config,
942    ) -> Result<Self::Output, ParseError>;
943
944    #[inline]
945    fn parse_whole(&mut self) -> Whole {
946        let BytesRange(start, end) = self.bytes().parse_digits_strip_zeros();
947        Whole(start, end)
948    }
949
950    #[inline]
951    fn parse_numeral(
952        &'_ mut self,
953        numerals: Option<&'a dyn NumbersLike>,
954        config: &'a Config,
955    ) -> Result<Option<(&'a str, Multiplier)>, ParseError> {
956        if let Some(numerals) = numerals {
957            let bytes = self.bytes();
958            let start = bytes.current_pos;
959            let buffer = bytes.buffered_advance_to(config.inner_delimiter);
960
961            if buffer.is_empty() {
962                // Haven't found a way to trigger this line, so it's excluded from coverage for now
963                return Ok(None); // cov:excl-line
964            }
965            // SAFETY: we've only parsed valid utf-8 up to this point and the delimiter only matches
966            // ascii
967            let string = unsafe { std::str::from_utf8_unchecked(buffer) };
968            return match numerals.get(string) {
969                None => {
970                    bytes.reset(start);
971                    Ok(None)
972                }
973                some_option => match bytes.current_byte {
974                    Some(byte) if (config.inner_delimiter)(*byte) => {
975                        bytes.try_consume_delimiter(config.inner_delimiter)?;
976                        Ok(some_option.map(|m| (string, m)))
977                    }
978                    None | Some(_) => {
979                        bytes.reset(start);
980                        Ok(None)
981                    }
982                },
983            };
984        }
985        Ok(None)
986    }
987
988    fn parse(
989        &'a mut self,
990        config: &'a Config,
991        time_units: &dyn TimeUnitsLike,
992        keywords: Option<&dyn TimeUnitsLike>,
993        numerals: Option<&'a dyn NumbersLike>,
994    ) -> Result<Self::Output, ParseError> {
995        if self.bytes().current_byte.is_none() {
996            return Err(ParseError::Empty);
997        }
998
999        let mut duration_repr = DurationRepr {
1000            default_unit: config.default_unit,
1001            input: self.bytes().input,
1002            ..Default::default()
1003        };
1004
1005        self.parse_number_sign(&mut duration_repr, config)?;
1006
1007        // parse infinity, keywords, ... or the whole number part of the input
1008        match self.bytes().current_byte.copied() {
1009            Some(byte) if byte.is_ascii_digit() => {
1010                duration_repr.whole = Some(self.parse_whole());
1011            }
1012            Some(b'.') => {}
1013            Some(_)
1014                if !config.disable_infinity && self.bytes().next_is_ignore_ascii_case(b"inf") =>
1015            {
1016                // SAFETY: We just checked that there are at least 3 bytes
1017                unsafe { self.bytes().advance_by(3) }
1018                return self.parse_infinity_remainder(duration_repr, config);
1019            }
1020            Some(_) => {
1021                if let Some((unit, multi)) = self.parse_keyword(keywords, config)? {
1022                    duration_repr.unit = Some(unit);
1023                    duration_repr.multiplier = multi;
1024                    return self.finalize(duration_repr, config);
1025                }
1026                if config.number_is_optional {
1027                    let start = self.bytes().current_pos;
1028                    match self.parse_time_unit(config, time_units)? {
1029                        Some((time_unit, multiplier)) => {
1030                            duration_repr.unit = Some(time_unit);
1031                            duration_repr.multiplier = multiplier;
1032                            return self.finalize(duration_repr, config);
1033                        }
1034                        None => {
1035                            self.bytes().reset(start);
1036                        }
1037                    }
1038                }
1039                if let Some((id, numeral)) = self.parse_numeral(numerals, config)? {
1040                    match self.parse_time_unit(config, time_units)? {
1041                        Some((time_unit, multiplier)) => {
1042                            duration_repr.numeral = Some(numeral);
1043                            duration_repr.unit = Some(time_unit);
1044                            duration_repr.multiplier = multiplier;
1045                            return self.finalize(duration_repr, config);
1046                        }
1047                        None if time_units.is_empty() => {
1048                            return Err(ParseError::TimeUnit(
1049                                self.bytes().current_pos,
1050                                format!("Found numeral '{id}' without time units being defined"),
1051                            ));
1052                        }
1053                        None => {
1054                            return Err(ParseError::TimeUnit(
1055                                self.bytes().current_pos,
1056                                format!("Found numeral '{id}' without a time unit"),
1057                            ));
1058                        }
1059                    }
1060                }
1061                return self
1062                    .bytes()
1063                    .get_remainder_str()
1064                    .and_then(|remainder| Err(ParseError::InvalidInput(remainder.to_owned())));
1065            }
1066            // This is currently unreachable code since empty input and a standalone sign are
1067            // already handled as errors before. However, keep this code as safety net.
1068            // cov:excl-start
1069            None => {
1070                return Err(ParseError::Syntax(
1071                    self.bytes().current_pos,
1072                    "Unexpected end of input".to_owned(),
1073                ));
1074            } // cov:excl-stop
1075        }
1076
1077        if !self.parse_number_fraction(&mut duration_repr, config.disable_fraction)? {
1078            return Ok(self.make_output(duration_repr));
1079        }
1080
1081        if !self.parse_number_exponent(&mut duration_repr, config.disable_exponent)? {
1082            return Ok(self.make_output(duration_repr));
1083        }
1084
1085        if !self.parse_number_delimiter(
1086            config
1087                .allow_time_unit_delimiter
1088                .then_some(config.inner_delimiter),
1089        )? {
1090            return Ok(self.make_output(duration_repr));
1091        }
1092
1093        if !self.parse_number_time_unit(&mut duration_repr, config, time_units)? {
1094            // Currently unreachable but let's keep it for clarity and safety especially because
1095            // the parse_number_time_unit must be implemented by this traits implementations
1096            return Ok(self.make_output(duration_repr)); // cov:excl-line
1097        }
1098
1099        self.finalize(duration_repr, config)
1100    }
1101
1102    /// Parse and consume the sign if present. Return true if sign is negative.
1103    fn parse_sign_is_negative(&mut self) -> Result<Option<bool>, ParseError> {
1104        let bytes = self.bytes();
1105        match bytes.current_byte {
1106            Some(byte) if *byte == b'+' => {
1107                bytes.advance();
1108                Ok(Some(false))
1109            }
1110            Some(byte) if *byte == b'-' => {
1111                bytes.advance();
1112                Ok(Some(true))
1113            }
1114            Some(_) => Ok(None),
1115            None => Err(ParseError::Syntax(
1116                bytes.current_pos,
1117                "Unexpected end of input".to_owned(),
1118            )),
1119        }
1120    }
1121
1122    fn parse_number_sign(
1123        &mut self,
1124        duration_repr: &mut DurationRepr,
1125        config: &Config,
1126    ) -> Result<(), ParseError> {
1127        if let Some(is_negative) = self.parse_sign_is_negative()? {
1128            duration_repr.is_negative = Some(is_negative);
1129
1130            let bytes = self.bytes();
1131            match bytes.current_byte {
1132                Some(byte) if config.allow_sign_delimiter && (config.inner_delimiter)(*byte) => {
1133                    return bytes.try_consume_delimiter(config.inner_delimiter);
1134                }
1135                Some(_) => {}
1136                None => {
1137                    return Err(ParseError::Syntax(
1138                        bytes.current_pos,
1139                        "Unexpected end of input. Sign without a number".to_owned(),
1140                    ));
1141                }
1142            }
1143        }
1144        Ok(())
1145    }
1146
1147    #[inline]
1148    fn parse_fract(&mut self) -> Fract {
1149        let BytesRange(start, end) = self.bytes().parse_digits();
1150        Fract(start, end)
1151    }
1152
1153    fn parse_number_fraction(
1154        &mut self,
1155        duration_repr: &mut DurationRepr<'a>,
1156        disable_fraction: bool,
1157    ) -> Result<bool, ParseError> {
1158        let bytes = self.bytes();
1159        match bytes.current_byte {
1160            Some(byte) if *byte == b'.' && !disable_fraction => {
1161                bytes.advance();
1162                let fract = match bytes.current_byte {
1163                    Some(byte) if byte.is_ascii_digit() => Some(self.parse_fract()),
1164                    Some(_) | None if duration_repr.whole.is_none() => {
1165                        // Use the decimal point as anchor for the error position. Subtraction by 1
1166                        // is safe since we were advancing by one before.
1167                        return Err(ParseError::Syntax(
1168                            bytes.current_pos - 1,
1169                            "Either the whole number part or the fraction must be present"
1170                                .to_owned(),
1171                        ));
1172                    }
1173                    Some(_) => Some(Fract(self.bytes().current_pos, self.bytes().current_pos)),
1174                    None => {
1175                        duration_repr.fract =
1176                            Some(Fract(self.bytes().current_pos, self.bytes().current_pos));
1177                        return Ok(false);
1178                    }
1179                };
1180                duration_repr.fract = fract;
1181                Ok(true)
1182            }
1183            Some(byte) if *byte == b'.' => Err(ParseError::Syntax(
1184                bytes.current_pos,
1185                "No fraction allowed".to_owned(),
1186            )),
1187            Some(_) => Ok(true),
1188            None => Ok(false),
1189        }
1190    }
1191
1192    fn parse_exponent(&mut self) -> Result<i16, ParseError> {
1193        let is_negative = self.parse_sign_is_negative()?.unwrap_or_default();
1194        let bytes = self.bytes();
1195
1196        let mut exponent = 0i16;
1197        let start = bytes.current_pos;
1198        while let Some(byte) = bytes.current_byte {
1199            let digit = byte.wrapping_sub(b'0');
1200            if digit < 10 {
1201                exponent = if is_negative {
1202                    match exponent
1203                        .checked_mul(10)
1204                        .and_then(|e| e.checked_sub(i16::from(digit)))
1205                    {
1206                        Some(exponent) => exponent,
1207                        None => return Err(ParseError::NegativeExponentOverflow),
1208                    }
1209                } else {
1210                    match exponent
1211                        .checked_mul(10)
1212                        .and_then(|e| e.checked_add(i16::from(digit)))
1213                    {
1214                        Some(exponent) => exponent,
1215                        None => return Err(ParseError::PositiveExponentOverflow),
1216                    }
1217                };
1218                bytes.advance();
1219            } else {
1220                break;
1221            }
1222        }
1223
1224        if bytes.current_pos - start > 0 {
1225            Ok(exponent)
1226        } else if bytes.is_end_of_input() {
1227            Err(ParseError::Syntax(
1228                bytes.current_pos,
1229                "Expected exponent but reached end of input".to_owned(),
1230            ))
1231        } else {
1232            Err(ParseError::Syntax(
1233                bytes.current_pos,
1234                "The exponent must have at least one digit".to_owned(),
1235            ))
1236        }
1237    }
1238
1239    fn parse_number_exponent(
1240        &mut self,
1241        duration_repr: &mut DurationRepr<'a>,
1242        disable_exponent: bool,
1243    ) -> Result<bool, ParseError> {
1244        let bytes = self.bytes();
1245        match bytes.current_byte {
1246            Some(byte) if byte.eq_ignore_ascii_case(&b'e') && !disable_exponent => {
1247                bytes.advance();
1248                duration_repr.exponent = self.parse_exponent()?;
1249                Ok(true)
1250            }
1251            Some(byte) if byte.eq_ignore_ascii_case(&b'e') => Err(ParseError::Syntax(
1252                bytes.current_pos,
1253                "No exponent allowed".to_owned(),
1254            )),
1255            Some(_) => Ok(true),
1256            None => Ok(false),
1257        }
1258    }
1259
1260    fn parse_number_delimiter(&mut self, delimiter: Option<Delimiter>) -> Result<bool, ParseError> {
1261        let bytes = self.bytes();
1262
1263        // If allow_time_unit_delimiter is true and there are any delimiters between the number and
1264        // the time unit, the delimiters are consumed before trying to parse the time units
1265        match (bytes.current_byte, delimiter) {
1266            (Some(byte), Some(delimiter)) if delimiter(*byte) => {
1267                bytes.try_consume_delimiter(delimiter)?;
1268                Ok(true)
1269            }
1270            (Some(_), _) => Ok(true),
1271            (None, _) => Ok(false),
1272        }
1273    }
1274}
1275
1276pub struct ReprParserSingle<'a> {
1277    pub bytes: Bytes<'a>,
1278}
1279
1280impl<'a> ReprParserSingle<'a> {
1281    pub const fn new(input: &'a str) -> Self {
1282        Self {
1283            bytes: Bytes::new(input.as_bytes()),
1284        }
1285    }
1286}
1287
1288impl<'a> ReprParserTemplate<'a> for ReprParserSingle<'a> {
1289    type Output = DurationRepr<'a>;
1290
1291    #[inline]
1292    fn bytes(&mut self) -> &mut Bytes<'a> {
1293        &mut self.bytes
1294    }
1295
1296    #[inline]
1297    fn make_output(&'a mut self, duration_repr: DurationRepr<'a>) -> Self::Output {
1298        duration_repr
1299    }
1300
1301    #[inline]
1302    fn parse_infinity_remainder(
1303        &'a mut self,
1304        mut duration_repr: DurationRepr<'a>,
1305        _: &Config,
1306    ) -> Result<DurationRepr<'a>, ParseError> {
1307        if self.bytes.is_end_of_input() {
1308            duration_repr.is_infinite = true;
1309            return Ok(duration_repr);
1310        }
1311
1312        let expected = b"inity";
1313        for byte in expected {
1314            match self.bytes.current_byte {
1315                Some(current) if current.eq_ignore_ascii_case(byte) => self.bytes.advance(),
1316                // wrong character
1317                Some(current) => {
1318                    return Err(ParseError::Syntax(
1319                        self.bytes.current_pos,
1320                        format!(
1321                            "Error parsing infinity: Invalid character '{}'",
1322                            *current as char
1323                        ),
1324                    ));
1325                }
1326                None => {
1327                    return Err(ParseError::Syntax(
1328                        self.bytes.current_pos,
1329                        "Error parsing infinity: Premature end of input".to_owned(),
1330                    ));
1331                }
1332            }
1333        }
1334
1335        duration_repr.is_infinite = true;
1336        self.bytes.check_end_of_input().map(|()| duration_repr)
1337    }
1338
1339    #[inline]
1340    fn parse_keyword(
1341        &mut self,
1342        keywords: Option<&dyn TimeUnitsLike>,
1343        _: &Config,
1344    ) -> Result<Option<(TimeUnit, Multiplier)>, ParseError> {
1345        if let Some(keywords) = keywords {
1346            // SAFETY: we've only parsed valid utf-8 up to this point
1347            let keyword = unsafe { self.bytes.get_remainder_str_unchecked() };
1348            match keywords.get(keyword) {
1349                None => Ok(None),
1350                some_time_unit => {
1351                    self.bytes.finish();
1352                    Ok(some_time_unit)
1353                }
1354            }
1355        } else {
1356            Ok(None)
1357        }
1358    }
1359
1360    fn parse_time_unit(
1361        &mut self,
1362        config: &Config,
1363        time_units: &dyn TimeUnitsLike,
1364    ) -> Result<Option<(TimeUnit, Multiplier)>, ParseError> {
1365        // cov:excl-start
1366        debug_assert!(
1367            self.bytes.current_byte.is_some(),
1368            "Don't call this function without being sure there's at least 1 byte remaining"
1369        ); // cov:excl-stop
1370
1371        if config.allow_ago {
1372            let start = self.bytes.current_pos;
1373            // SAFETY: The delimiter may not match non-ascii bytes and we've parsed only valid utf-8
1374            // so far
1375            let string = unsafe {
1376                std::str::from_utf8_unchecked(self.bytes.advance_to(config.inner_delimiter))
1377            };
1378
1379            let (time_unit, mut multiplier) = if string.is_empty() {
1380                // Haven't found a way to trigger this line, so it's excluded from coverage for now
1381                return Ok(None); // cov:excl-line
1382            } else {
1383                match time_units.get(string) {
1384                    None => {
1385                        self.bytes.reset(start);
1386                        return Ok(None);
1387                    }
1388                    Some(unit) => unit,
1389                }
1390            };
1391
1392            // At this point, either there are one or more bytes of which the first is the
1393            // delimiter or we've reached the end of input
1394            if self.bytes.current_byte.is_some() {
1395                self.bytes.try_consume_delimiter(config.inner_delimiter)?;
1396                if self.bytes.next_is_ignore_ascii_case(b"ago") {
1397                    // SAFETY: We have checked that there are at least 3 bytes
1398                    unsafe { self.bytes.advance_by(3) };
1399                    // We're applying the negation on the multiplier only once so we don't need
1400                    // the operation to be reflexive and using saturating neg is fine
1401                    multiplier = multiplier.saturating_neg();
1402                } else {
1403                    self.bytes.reset(start);
1404                    return Ok(None);
1405                }
1406            };
1407
1408            Ok(Some((time_unit, multiplier)))
1409        } else {
1410            // SAFETY: The input of `parse` is &str and therefore valid utf-8 and we have read
1411            // only ascii characters up to this point.
1412            let string = unsafe { self.bytes.get_remainder_str_unchecked() };
1413            let result = match time_units.get(string) {
1414                None => return Ok(None),
1415                some_time_unit => Ok(some_time_unit),
1416            };
1417            self.bytes.finish();
1418            result
1419        }
1420    }
1421
1422    #[inline]
1423    fn parse_number_time_unit(
1424        &mut self,
1425        duration_repr: &mut DurationRepr<'a>,
1426        config: &'a Config,
1427        time_units: &dyn TimeUnitsLike,
1428    ) -> Result<bool, ParseError> {
1429        match self.bytes.current_byte {
1430            Some(_) if !time_units.is_empty() => {
1431                if let Some((unit, multi)) = self.parse_time_unit(config, time_units)? {
1432                    duration_repr.unit = Some(unit);
1433                    duration_repr.multiplier = multi;
1434                    Ok(true)
1435                } else {
1436                    self.bytes.get_remainder_str().and_then(|remainder| {
1437                        Err(ParseError::TimeUnit(
1438                            self.bytes.current_pos,
1439                            format!("Invalid time unit: '{remainder}'"),
1440                        ))
1441                    })
1442                }
1443            }
1444            Some(_) => {
1445                Err(ParseError::TimeUnit(
1446                    self.bytes.current_pos,
1447                    // SAFETY: We've parsed only valid utf-8 so far
1448                    format!("No time units allowed but found: '{}'", unsafe {
1449                        self.bytes.get_remainder_str_unchecked()
1450                    }),
1451                ))
1452            }
1453            // This branch is excluded from coverage because parsing with parse_number_delimiter
1454            // already ensures that there's at least 1 byte.
1455            None => Ok(false), // cov:excl-line
1456        }
1457    }
1458
1459    #[inline]
1460    fn finalize(
1461        &'a mut self,
1462        duration_repr: DurationRepr<'a>,
1463        _: &Config,
1464    ) -> Result<Self::Output, ParseError> {
1465        self.bytes.check_end_of_input().map(|()| duration_repr)
1466    }
1467}
1468
1469pub struct ReprParserMultiple<'a> {
1470    pub bytes: Bytes<'a>,
1471}
1472
1473impl<'a> ReprParserMultiple<'a> {
1474    pub fn new(input: &'a str) -> Self {
1475        Self {
1476            bytes: Bytes::new(input.as_bytes()),
1477        }
1478    }
1479
1480    #[inline]
1481    pub fn is_next_duration(byte: u8) -> bool {
1482        byte.is_ascii_digit() || byte == b'+' || byte == b'-'
1483    }
1484
1485    pub fn try_consume_connection(
1486        &mut self,
1487        delimiter: Delimiter,
1488        conjunctions: &'a [&'a str],
1489    ) -> Result<(), ParseError> {
1490        debug_assert!(delimiter(*self.bytes.current_byte.unwrap()));
1491
1492        self.bytes.try_consume_delimiter(delimiter)?;
1493        let start = self.bytes.current_pos;
1494        // try_consume_delimiter ensures there's at least one byte here
1495        for word in conjunctions {
1496            if self.bytes.next_is_ignore_ascii_case(word.as_bytes()) {
1497                // SAFETY: We're advancing by the amount of bytes of the word we just found
1498                unsafe { self.bytes.advance_by(word.len()) };
1499                match self.bytes.current_byte {
1500                    Some(byte) if delimiter(*byte) => {
1501                        self.bytes.try_consume_delimiter(delimiter)?;
1502                    }
1503                    Some(byte) if Self::is_next_duration(*byte) => {}
1504                    Some(byte) => {
1505                        return Err(ParseError::Syntax(
1506                            self.bytes.current_pos,
1507                            format!(
1508                                "A conjunction must be separated by a delimiter, sign or digit \
1509                                 but found: '{}'",
1510                                *byte as char
1511                            ),
1512                        ));
1513                    }
1514                    None => {
1515                        return Err(ParseError::Syntax(
1516                            start,
1517                            format!("Input may not end with a conjunction but found: '{word}'"),
1518                        ));
1519                    }
1520                }
1521                break;
1522            }
1523        }
1524        Ok(())
1525    }
1526}
1527
1528impl<'a> ReprParserTemplate<'a> for ReprParserMultiple<'a> {
1529    type Output = (DurationRepr<'a>, Option<&'a mut ReprParserMultiple<'a>>);
1530
1531    #[inline]
1532    fn bytes(&mut self) -> &mut Bytes<'a> {
1533        &mut self.bytes
1534    }
1535
1536    #[inline]
1537    fn make_output(&'a mut self, duration_repr: DurationRepr<'a>) -> Self::Output {
1538        (duration_repr, self.bytes().current_byte.map(|_| self))
1539    }
1540
1541    #[inline]
1542    fn parse_infinity_remainder(
1543        &'a mut self,
1544        mut duration_repr: DurationRepr<'a>,
1545        config: &'a Config,
1546    ) -> Result<(DurationRepr<'a>, Option<&'a mut ReprParserMultiple<'a>>), ParseError> {
1547        match self.bytes.current_byte {
1548            Some(byte) if (config.outer_delimiter)(*byte) => {
1549                duration_repr.is_infinite = true;
1550                return self
1551                    .try_consume_connection(
1552                        config.outer_delimiter,
1553                        config.conjunctions.unwrap_or_default(),
1554                    )
1555                    .map(|()| (duration_repr, Some(self)));
1556            }
1557            Some(_) => {}
1558            None => {
1559                duration_repr.is_infinite = true;
1560                return Ok((duration_repr, None));
1561            }
1562        }
1563
1564        let expected = "inity";
1565        let start = self.bytes.current_pos;
1566        for byte in expected.as_bytes() {
1567            match self.bytes.current_byte {
1568                Some(current) if current.eq_ignore_ascii_case(byte) => self.bytes.advance(),
1569                // wrong character
1570                Some(current) => {
1571                    return Err(ParseError::Syntax(
1572                        self.bytes.current_pos,
1573                        format!(
1574                            "Error parsing infinity: Invalid character '{}'",
1575                            *current as char
1576                        ),
1577                    ));
1578                }
1579                None => {
1580                    return Err(ParseError::Syntax(
1581                        // This subtraction is safe since we're here only if there's at least `inf`
1582                        // present
1583                        start - 3,
1584                        format!(
1585                            "Error parsing infinity: 'inf{}' is an invalid identifier for infinity",
1586                            self.bytes.get_current_str(start).unwrap() // unwrap is safe
1587                        ),
1588                    ));
1589                }
1590            }
1591        }
1592
1593        duration_repr.is_infinite = true;
1594        match self.bytes.current_byte {
1595            Some(byte) if (config.outer_delimiter)(*byte) => {
1596                self.try_consume_connection(
1597                    config.outer_delimiter,
1598                    config.conjunctions.unwrap_or_default(),
1599                )?;
1600                Ok((duration_repr, Some(self)))
1601            }
1602            Some(byte) => Err(ParseError::Syntax(
1603                self.bytes.current_pos,
1604                format!(
1605                    "Error parsing infinity: Expected a delimiter but found '{}'",
1606                    *byte as char
1607                ),
1608            )),
1609            None => Ok((duration_repr, None)),
1610        }
1611    }
1612
1613    #[inline]
1614    fn parse_keyword(
1615        &mut self,
1616        keywords: Option<&dyn TimeUnitsLike>,
1617        config: &'a Config,
1618    ) -> Result<Option<(TimeUnit, Multiplier)>, ParseError> {
1619        if let Some(keywords) = keywords {
1620            let start = self.bytes.current_pos;
1621            let buffer = self.bytes.buffered_advance_to(|byte: u8| {
1622                (config.outer_delimiter)(byte) || Self::is_next_duration(byte)
1623            });
1624
1625            if buffer.is_empty() {
1626                // Haven't found a way to trigger this line, so it's excluded from coverage for now
1627                return Ok(None); // cov:excl-line
1628            }
1629
1630            // SAFETY: The delimiter may not match non-ascii bytes and we've parsed only valid utf-8
1631            // so far
1632            let string = unsafe { std::str::from_utf8_unchecked(buffer) };
1633
1634            match keywords.get(string) {
1635                None => {
1636                    self.bytes.reset(start);
1637                    Ok(None)
1638                }
1639                some_time_unit => {
1640                    if let Some(byte) = self.bytes.current_byte {
1641                        if (config.outer_delimiter)(*byte) {
1642                            self.try_consume_connection(
1643                                config.outer_delimiter,
1644                                config.conjunctions.unwrap_or_default(),
1645                            )?;
1646                        }
1647                    }
1648                    Ok(some_time_unit)
1649                }
1650            }
1651        } else {
1652            Ok(None)
1653        }
1654    }
1655
1656    #[inline]
1657    fn parse_time_unit(
1658        &mut self,
1659        config: &'a Config,
1660        time_units: &dyn TimeUnitsLike,
1661    ) -> Result<Option<(TimeUnit, Multiplier)>, ParseError> {
1662        // cov:excl-start
1663        debug_assert!(
1664            self.bytes.current_byte.is_some(),
1665            "Don't call this function without being sure there's at least 1 byte remaining"
1666        ); // cov:excl-stop
1667
1668        let start = self.bytes.current_pos;
1669        let buffer = if config.allow_ago {
1670            self.bytes.buffered_advance_to(|byte: u8| {
1671                (config.inner_delimiter)(byte)
1672                    || (config.outer_delimiter)(byte)
1673                    || Self::is_next_duration(byte)
1674            })
1675        } else {
1676            self.bytes.buffered_advance_to(|byte: u8| {
1677                (config.outer_delimiter)(byte) || Self::is_next_duration(byte)
1678            })
1679        };
1680        if buffer.is_empty() {
1681            return Ok(None);
1682        }
1683
1684        // SAFETY: The delimiter may not match non-ascii bytes and we've parsed only valid utf-8 so
1685        // far
1686        let string = unsafe { std::str::from_utf8_unchecked(buffer) };
1687
1688        let (time_unit, mut multiplier) = match time_units.get(string) {
1689            None => {
1690                self.bytes.reset(start);
1691                return Ok(None);
1692            }
1693            Some(some_time_unit) => some_time_unit,
1694        };
1695
1696        match self.bytes.current_byte {
1697            Some(byte) if config.allow_ago && (config.inner_delimiter)(*byte) => {
1698                let start = self.bytes.current_pos;
1699                self.bytes.try_consume_delimiter(config.inner_delimiter)?;
1700                if self.bytes.next_is_ignore_ascii_case(b"ago") {
1701                    // SAFETY: We know that next is `ago` which has 3 bytes
1702                    unsafe { self.bytes.advance_by(3) };
1703                    match self.bytes.current_byte {
1704                        Some(byte)
1705                            if (config.outer_delimiter)(*byte) || Self::is_next_duration(*byte) =>
1706                        {
1707                            multiplier = multiplier.saturating_neg();
1708                        }
1709                        Some(_) => {
1710                            self.bytes.reset(start);
1711                        }
1712                        None => {
1713                            multiplier = multiplier.saturating_neg();
1714                        }
1715                    }
1716                } else {
1717                    self.bytes.reset(start);
1718                }
1719            }
1720            _ => {}
1721        }
1722
1723        match self.bytes.current_byte {
1724            Some(byte) if (config.outer_delimiter)(*byte) => {
1725                self.try_consume_connection(
1726                    config.outer_delimiter,
1727                    config.conjunctions.unwrap_or_default(),
1728                )?;
1729            }
1730            Some(_) | None => {}
1731        }
1732
1733        Ok(Some((time_unit, multiplier)))
1734    }
1735
1736    #[inline]
1737    fn parse_number_time_unit(
1738        &mut self,
1739        duration_repr: &mut DurationRepr<'a>,
1740        config: &'a Config,
1741        time_units: &dyn TimeUnitsLike,
1742    ) -> Result<bool, ParseError> {
1743        match self.bytes().current_byte {
1744            Some(_) if !time_units.is_empty() => {
1745                if let Some((unit, multi)) = self.parse_time_unit(config, time_units)? {
1746                    duration_repr.unit = Some(unit);
1747                    duration_repr.multiplier = multi;
1748                }
1749            }
1750            Some(_) => {}
1751            // This branch is excluded from coverage because parse_number_delimiter already ensures
1752            // that there's at least 1 byte.
1753            None => return Ok(false), // cov:excl-line
1754        }
1755        Ok(true)
1756    }
1757
1758    #[inline]
1759    fn finalize(
1760        &'a mut self,
1761        duration_repr: DurationRepr<'a>,
1762        config: &'a Config,
1763    ) -> Result<Self::Output, ParseError> {
1764        match self.bytes().current_byte {
1765            Some(byte) if (config.outer_delimiter)(*byte) => self
1766                .try_consume_connection(
1767                    config.outer_delimiter,
1768                    config.conjunctions.unwrap_or_default(),
1769                )
1770                .map(|()| (duration_repr, Some(self))),
1771            Some(_) => Ok((duration_repr, Some(self))),
1772            None => Ok((duration_repr, None)),
1773        }
1774    }
1775}
1776
1777#[cfg(test)]
1778mod tests {
1779    use rstest::rstest;
1780    use rstest_reuse::{apply, template};
1781
1782    use super::*;
1783
1784    struct TimeUnitsFixture;
1785
1786    // cov:excl-start This is just a fixture
1787    impl TimeUnitsLike for TimeUnitsFixture {
1788        fn is_empty(&self) -> bool {
1789            true
1790        }
1791
1792        fn get(&self, _: &str) -> Option<(TimeUnit, Multiplier)> {
1793            None
1794        }
1795    } // cov:excl-stop
1796
1797    #[rstest]
1798    #[case::zeros("00000000", Some(0x3030_3030_3030_3030))]
1799    #[case::one("00000001", Some(0x3130_3030_3030_3030))]
1800    #[case::ten_millions("10000000", Some(0x3030_3030_3030_3031))]
1801    #[case::nines("99999999", Some(0x3939_3939_3939_3939))]
1802    fn test_duration_repr_parser_parse_8_digits(
1803        #[case] input: &str,
1804        #[case] expected: Option<u64>,
1805    ) {
1806        let mut parser = ReprParserSingle::new(input);
1807        assert_eq!(parser.bytes.parse_8_digits(), expected);
1808    }
1809
1810    #[rstest]
1811    #[case::empty("", None)]
1812    #[case::one_non_digit_char("a0000000", None)]
1813    #[case::less_than_8_digits("9999999", None)]
1814    fn test_duration_repr_parser_parse_8_digits_when_not_8_digits(
1815        #[case] input: &str,
1816        #[case] expected: Option<u64>,
1817    ) {
1818        let mut parser = ReprParserSingle::new(input);
1819        assert_eq!(parser.bytes.parse_8_digits(), expected);
1820        assert_eq!(parser.bytes.get_remainder(), input.as_bytes());
1821        assert_eq!(parser.bytes.current_byte, input.as_bytes().first());
1822        assert_eq!(parser.bytes.current_pos, 0);
1823    }
1824
1825    #[test]
1826    fn test_duration_repr_parser_parse_8_digits_when_more_than_8() {
1827        let mut parser = ReprParserSingle::new("00000000a");
1828        assert_eq!(parser.bytes.parse_8_digits(), Some(0x3030_3030_3030_3030));
1829        assert_eq!(parser.bytes.get_remainder(), b"a");
1830        assert_eq!(parser.bytes.current_byte, Some(&b'a'));
1831        assert_eq!(parser.bytes.current_pos, 8);
1832    }
1833
1834    #[template]
1835    #[rstest]
1836    #[case::zero("0", Whole(1, 1))]
1837    #[case::one("1", Whole(0, 1))]
1838    #[case::nine("9", Whole(0, 1))]
1839    #[case::ten("10", Whole(0, 2))]
1840    #[case::eight_leading_zeros("00000000", Whole(8, 8))]
1841    #[case::fifteen_leading_zeros("000000000000000", Whole(15, 15))]
1842    #[case::ten_with_leading_zeros_when_eight_digits("00000010", Whole(6, 8))]
1843    #[case::ten_with_leading_zeros_when_nine_digits("000000010", Whole(7, 9))]
1844    #[case::mixed_number("12345", Whole(0, 5))]
1845    #[case::max_8_digits("99999999", Whole(0, 8))]
1846    #[case::max_8_digits_minus_one("99999998", Whole(0, 8))]
1847    #[case::min_nine_digits("100000000", Whole(0, 9))]
1848    #[case::min_nine_digits_plus_one("100000001", Whole(0, 9))]
1849    #[case::eight_zero_digits_start("0000000011111111", Whole(8, 16))]
1850    #[case::eight_zero_digits_end("1111111100000000", Whole(0, 16))]
1851    #[case::eight_zero_digits_middle("11111111000000001", Whole(0, 17))]
1852    #[case::max_16_digits("9999999999999999", Whole(0, 16))]
1853    fn test_duration_repr_parser_parse_whole(#[case] input: &str, #[case] expected: Whole) {}
1854
1855    #[apply(test_duration_repr_parser_parse_whole)]
1856    fn test_duration_repr_parser_parse_whole_single(input: &str, expected: Whole) {
1857        let mut parser = ReprParserSingle::new(input);
1858        assert_eq!(parser.parse_whole(), expected);
1859    }
1860
1861    #[apply(test_duration_repr_parser_parse_whole)]
1862    fn test_duration_repr_parser_parse_whole_multiple(input: &str, expected: Whole) {
1863        let mut parser = ReprParserMultiple::new(input); // cov:excl-line
1864        assert_eq!(parser.parse_whole(), expected);
1865    }
1866
1867    // TODO: add test case for parse_multiple
1868    #[test]
1869    fn test_duration_repr_parser_parse_whole_when_more_than_max_exponent() {
1870        let config = Config::new();
1871        let input = &"1".repeat(i16::MAX as usize + 100);
1872        let mut parser = ReprParserSingle::new(input);
1873        let duration_repr = parser
1874            .parse(&config, &TimeUnitsFixture, None, None)
1875            .unwrap();
1876        assert_eq!(duration_repr.whole, Some(Whole(0, i16::MAX as usize + 100)));
1877        assert_eq!(duration_repr.fract, None);
1878    }
1879
1880    // TODO: use own test case for parse_multiple
1881    #[test]
1882    fn test_duration_repr_parser_parse_fract_when_more_than_max_exponent() {
1883        let input = format!(".{}", "1".repeat(i16::MAX as usize + 100));
1884        let config = Config::new();
1885
1886        let mut parser = ReprParserSingle::new(&input);
1887        let duration_repr = parser
1888            .parse(&config, &TimeUnitsFixture, None, None)
1889            .unwrap();
1890        assert_eq!(duration_repr.whole, None);
1891        assert_eq!(duration_repr.fract, Some(Fract(1, i16::MAX as usize + 101)));
1892
1893        let mut config = Config::new();
1894        config.allow_multiple = true;
1895        let mut parser = ReprParserMultiple::new(&input);
1896        let (duration_repr, maybe_parser) = parser
1897            .parse(&config, &TimeUnitsFixture, None, None)
1898            .unwrap();
1899        assert!(maybe_parser.is_none());
1900        assert_eq!(duration_repr.whole, None);
1901        assert_eq!(duration_repr.fract, Some(Fract(1, i16::MAX as usize + 101)));
1902    }
1903
1904    #[template]
1905    #[rstest]
1906    #[case::zero("0", Fract(0, 1))]
1907    #[case::one("1", Fract(0, 1))]
1908    #[case::nine("9", Fract(0, 1))]
1909    #[case::ten("10", Fract(0, 2))]
1910    #[case::leading_zero("01", Fract(0, 2))]
1911    #[case::leading_zeros("001", Fract(0, 3))]
1912    #[case::eight_leading_zeros("000000001", Fract(0, 9))]
1913    #[case::mixed_number("12345", Fract(0, 5))]
1914    #[case::max_8_digits("99999999", Fract(0, 8))]
1915    #[case::max_8_digits_minus_one("99999998", Fract(0, 8))]
1916    #[case::nine_digits("123456789", Fract(0, 9))]
1917    fn test_duration_repr_parser_parse_fract(#[case] input: &str, #[case] expected: Fract) {}
1918
1919    #[apply(test_duration_repr_parser_parse_fract)]
1920    fn test_duration_repr_parser_parse_fract_single(input: &str, expected: Fract) {
1921        let mut parser = ReprParserSingle::new(input);
1922        assert_eq!(parser.parse_fract(), expected);
1923    }
1924
1925    #[apply(test_duration_repr_parser_parse_fract)]
1926    fn test_duration_repr_parser_parse_fract_multiple(input: &str, expected: Fract) {
1927        let mut parser = ReprParserMultiple::new(input); // cov:excl-line
1928        assert_eq!(parser.parse_fract(), expected);
1929    }
1930
1931    #[test]
1932    fn test_fract_is_empty() {
1933        assert!(Fract(0, 0).is_empty());
1934        assert!(Fract(9, 9).is_empty());
1935    }
1936
1937    #[test]
1938    fn test_whole_is_empty() {
1939        assert!(Whole(0, 0).is_empty());
1940        assert!(Whole(9, 9).is_empty());
1941    }
1942
1943    #[rstest]
1944    #[case::one_digit(b"1", None, None, 100_000_000_000_000_000)]
1945    #[case::one_digit_one_zero(b"1", None, Some(1), 10_000_000_000_000_000)]
1946    #[case::two_digits(b"12", None, None, 120_000_000_000_000_000)]
1947    #[case::two_digits_one_zero(b"12", None, Some(1), 12_000_000_000_000_000)]
1948    #[case::one_digit_prepend(b"2", Some(b"1".as_ref()), None, 120_000_000_000_000_000)]
1949    #[case::empty_digits_prepend_one(b"", Some(b"1".as_ref()), None, 100_000_000_000_000_000)]
1950    #[case::more_than_18_digits_when_zeros(b"234", Some(b"1".as_ref()), Some(16), 12)]
1951    fn test_fract(
1952        #[case] digits: &[u8],
1953        #[case] prepend: Option<&[u8]>,
1954        #[case] zeros: Option<usize>,
1955        #[case] expected: u64,
1956    ) {
1957        assert_eq!(Fract::parse(digits, prepend, zeros), expected);
1958    }
1959
1960    #[test]
1961    fn test_try_consume_delimiter_when_input_starts_with_delimiter_then_error() {
1962        let mut bytes = Bytes::new(b" some");
1963        assert_eq!(
1964            bytes.try_consume_delimiter(|byte| byte == b' '),
1965            Err(ParseError::Syntax(
1966                0,
1967                "Input may not start with a delimiter".to_owned()
1968            ))
1969        );
1970    }
1971}