/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.35/src/util/parse.rs
Line | Count | Source |
1 | | use jcore::bounds::Bounds; |
2 | | |
3 | | use crate::{ |
4 | | error::util::{ParseFractionError, ParseIntError}, |
5 | | Error, |
6 | | }; |
7 | | |
8 | | /// Parses an `i64` number from the beginning to the end of the given slice of |
9 | | /// ASCII digit characters. |
10 | | /// |
11 | | /// If any byte in the given slice is not `[0-9]`, then this returns an error. |
12 | | /// Similarly, if the number parsed does not fit into a `i64`, then this |
13 | | /// returns an error. Notably, this routine does not permit parsing a negative |
14 | | /// integer. (We use `i64` because everything in this crate uses signed |
15 | | /// integers, and because a higher level routine might want to parse the sign |
16 | | /// and then apply it to the result of this routine.) |
17 | | #[cfg_attr(feature = "perf-inline", inline(always))] |
18 | 0 | pub(crate) fn i64(bytes: &[u8]) -> Result<i64, ParseIntError> { |
19 | 0 | if bytes.is_empty() { |
20 | 0 | return Err(ParseIntError::NoDigitsFound); |
21 | 0 | } |
22 | 0 | let mut n: i64 = 0; |
23 | 0 | for &byte in bytes { |
24 | 0 | if !(b'0' <= byte && byte <= b'9') { |
25 | 0 | return Err(ParseIntError::InvalidDigit(byte)); |
26 | 0 | } |
27 | 0 | let digit = i64::from(byte - b'0'); |
28 | 0 | n = n |
29 | 0 | .checked_mul(10) |
30 | 0 | .and_then(|n| n.checked_add(digit)) |
31 | 0 | .ok_or(ParseIntError::TooBig)?; |
32 | | } |
33 | 0 | Ok(n) |
34 | 0 | } |
35 | | |
36 | | /// Like `self::i64`, but also does a boundary check for the given type. |
37 | | /// |
38 | | /// # Errors |
39 | | /// |
40 | | /// If the given slice is not a valid integer (i.e., overflow or contains |
41 | | /// anything other than `[0-9]`) or is not in the bounds for the given `Bounds` |
42 | | /// implementation, then an error is returned. |
43 | | /// |
44 | | /// Note that the error can either be a parsing error or it can be a |
45 | | /// boundary error. |
46 | | #[cfg_attr(feature = "perf-inline", inline(always))] |
47 | 0 | pub(crate) fn bi64<B>(bytes: &[u8]) -> Result<B::Primitive, Error> |
48 | 0 | where |
49 | 0 | B: Bounds, |
50 | 0 | Error: From<B::Error>, |
51 | | { |
52 | 0 | Ok(B::check(self::i64(bytes)?)?) |
53 | 0 | } Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::LeapSecond> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetHours> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetMinutes> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Day> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Hour> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Year> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Minute> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Month> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Year> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetHours> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetMinutes> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::OffsetSeconds> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::LeapSecond> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::WeekdayMondayOne> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Day> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Hour> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Month> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::Minute> Unexecuted instantiation: jiff::util::parse::bi64::<jiff::util::b::ISOWeek> |
54 | | |
55 | | /// Parsed an optional `u64` that is a prefix of `bytes`. |
56 | | /// |
57 | | /// If no digits (`[0-9]`) were found at the beginning of `bytes`, then `None` |
58 | | /// is returned. |
59 | | /// |
60 | | /// Note that this is safe to call on untrusted input. It will not attempt |
61 | | /// to consume more input than could possibly fit into a parsed integer. |
62 | | /// |
63 | | /// Since this returns a `u64`, it is possible that an integer that cannot |
64 | | /// fit into an `i64` is returned. Callers should handle this. (Indeed, |
65 | | /// `DurationUnits` handles this case.) |
66 | | /// |
67 | | /// # Errors |
68 | | /// |
69 | | /// When the parsed integer cannot fit into a `u64`. |
70 | | #[cfg_attr(feature = "perf-inline", inline(always))] |
71 | 0 | pub(crate) fn u64_prefix( |
72 | 0 | bytes: &[u8], |
73 | 0 | ) -> Result<(Option<u64>, &[u8]), ParseIntError> { |
74 | | // Discovered via `u64::MAX.to_string().len()`. |
75 | | const MAX_U64_DIGITS: usize = 20; |
76 | | |
77 | 0 | let mut digit_count = 0; |
78 | 0 | let mut n: u64 = 0; |
79 | 0 | while digit_count <= MAX_U64_DIGITS { |
80 | 0 | let Some(&byte) = bytes.get(digit_count) else { break }; |
81 | 0 | if !byte.is_ascii_digit() { |
82 | 0 | break; |
83 | 0 | } |
84 | 0 | digit_count += 1; |
85 | | // OK because we confirmed `byte` is an ASCII digit. |
86 | 0 | let digit = u64::from(byte - b'0'); |
87 | 0 | n = n |
88 | 0 | .checked_mul(10) |
89 | 0 | .and_then(|n| n.checked_add(digit)) |
90 | 0 | .ok_or(ParseIntError::TooBig)?; |
91 | | } |
92 | 0 | if digit_count == 0 { |
93 | 0 | return Ok((None, bytes)); |
94 | 0 | } |
95 | 0 | Ok((Some(n), &bytes[digit_count..])) |
96 | 0 | } |
97 | | |
98 | | /// Parses a `u32` fractional number from the beginning to the end of the given |
99 | | /// slice of ASCII digit characters. |
100 | | /// |
101 | | /// The fraction's maximum precision is always 9 digits. The returned integer |
102 | | /// will always be in units of `10^{max_precision}`. For example, this |
103 | | /// will parse a fractional amount of seconds with a maximum precision of |
104 | | /// nanoseconds. |
105 | | /// |
106 | | /// If any byte in the given slice is not `[0-9]`, then this returns an error. |
107 | | /// Notably, this routine does not permit parsing a negative integer. |
108 | 0 | pub(crate) fn fraction(bytes: &[u8]) -> Result<u32, ParseFractionError> { |
109 | 0 | if bytes.is_empty() { |
110 | 0 | return Err(ParseFractionError::NoDigitsFound); |
111 | 0 | } else if bytes.len() > ParseFractionError::MAX_PRECISION { |
112 | 0 | return Err(ParseFractionError::TooManyDigits); |
113 | 0 | } |
114 | 0 | let mut n: u32 = 0; |
115 | 0 | for &byte in bytes { |
116 | 0 | let digit = match byte.checked_sub(b'0') { |
117 | | None => { |
118 | 0 | return Err(ParseFractionError::InvalidDigit(byte)); |
119 | | } |
120 | 0 | Some(digit) if digit > 9 => { |
121 | 0 | return Err(ParseFractionError::InvalidDigit(byte)); |
122 | | } |
123 | 0 | Some(digit) => { |
124 | 0 | debug_assert!((0..=9).contains(&digit)); |
125 | 0 | u32::from(digit) |
126 | | } |
127 | | }; |
128 | 0 | n = n |
129 | 0 | .checked_mul(10) |
130 | 0 | .and_then(|n| n.checked_add(digit)) |
131 | 0 | .ok_or_else(|| ParseFractionError::TooBig)?; |
132 | | } |
133 | 0 | for _ in bytes.len()..ParseFractionError::MAX_PRECISION { |
134 | 0 | n = n.checked_mul(10).ok_or_else(|| ParseFractionError::TooBig)?; |
135 | | } |
136 | 0 | Ok(n) |
137 | 0 | } |
138 | | |
139 | | /// Parses an `OsStr` into a `&str` when `&[u8]` isn't easily available. |
140 | | /// |
141 | | /// This is effectively `OsStr::to_str`, but with a slightly better error |
142 | | /// message. |
143 | | #[cfg(any(feature = "tz-system", feature = "tzdb-zoneinfo"))] |
144 | 4.84k | pub(crate) fn os_str_utf8<'o, O>( |
145 | 4.84k | os_str: &'o O, |
146 | 4.84k | ) -> Result<&'o str, crate::error::util::OsStrUtf8Error> |
147 | 4.84k | where |
148 | 4.84k | O: ?Sized + AsRef<std::ffi::OsStr>, |
149 | | { |
150 | 4.84k | let os_str = os_str.as_ref(); |
151 | 4.84k | os_str |
152 | 4.84k | .to_str() |
153 | 4.84k | .ok_or_else(|| crate::error::util::OsStrUtf8Error::from(os_str)) |
154 | 4.84k | } |
155 | | |
156 | | /// Splits the given input into two slices at the given position. |
157 | | /// |
158 | | /// If the position is greater than the length of the slice given, then this |
159 | | /// returns `None`. |
160 | | #[cfg_attr(feature = "perf-inline", inline(always))] |
161 | 0 | pub(crate) fn split(input: &[u8], at: usize) -> Option<(&[u8], &[u8])> { |
162 | 0 | if at > input.len() { |
163 | 0 | None |
164 | | } else { |
165 | 0 | Some(input.split_at(at)) |
166 | | } |
167 | 0 | } |
168 | | |
169 | | /// Returns a function that converts two slices to an offset. |
170 | | /// |
171 | | /// It takes the starting point as input and returns a function that, when |
172 | | /// given an ending point (greater than or equal to the starting point), then |
173 | | /// the corresponding pointers are subtracted and an offset relative to the |
174 | | /// starting point is returned. |
175 | | /// |
176 | | /// This is useful as a helper function in parsing routines that use slices |
177 | | /// but want to report offsets. |
178 | | /// |
179 | | /// # Panics |
180 | | /// |
181 | | /// This may panic if the ending point is not a suffix slice of `start`. |
182 | 0 | pub(crate) fn offseter<'a>( |
183 | 0 | start: &'a [u8], |
184 | 0 | ) -> impl Fn(&'a [u8]) -> usize + 'a { |
185 | 0 | move |end| (end.as_ptr() as usize) - (start.as_ptr() as usize) |
186 | 0 | } |
187 | | |
188 | | /// Returns a function that converts two slices to the slice between them. |
189 | | /// |
190 | | /// This takes a starting point as input and returns a function that, when |
191 | | /// given an ending point (greater than or equal to the starting point), it |
192 | | /// returns a slice beginning at the starting point and ending just at the |
193 | | /// ending point. |
194 | | /// |
195 | | /// This is useful as a helper function in parsing routines. |
196 | | /// |
197 | | /// # Panics |
198 | | /// |
199 | | /// This may panic if the ending point is not a suffix slice of `start`. |
200 | 0 | pub(crate) fn slicer<'a>( |
201 | 0 | start: &'a [u8], |
202 | 0 | ) -> impl Fn(&'a [u8]) -> &'a [u8] + 'a { |
203 | 0 | let mkoffset = offseter(start); |
204 | 0 | move |end| { |
205 | 0 | let offset = mkoffset(end); |
206 | 0 | &start[..offset] |
207 | 0 | } |
208 | 0 | } |