Skip to main content

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