209 lines
6.9 KiB
Rust
209 lines
6.9 KiB
Rust
use jcore::bounds::Bounds;
|
|
|
|
use crate::{
|
|
error::util::{ParseFractionError, ParseIntError},
|
|
Error,
|
|
};
|
|
|
|
/// Parses an `i64` number from the beginning to the end of the given slice of
|
|
/// ASCII digit characters.
|
|
///
|
|
/// If any byte in the given slice is not `[0-9]`, then this returns an error.
|
|
/// Similarly, if the number parsed does not fit into a `i64`, then this
|
|
/// returns an error. Notably, this routine does not permit parsing a negative
|
|
/// integer. (We use `i64` because everything in this crate uses signed
|
|
/// integers, and because a higher level routine might want to parse the sign
|
|
/// and then apply it to the result of this routine.)
|
|
#[cfg_attr(feature = "perf-inline", inline(always))]
|
|
pub(crate) fn i64(bytes: &[u8]) -> Result<i64, ParseIntError> {
|
|
if bytes.is_empty() {
|
|
return Err(ParseIntError::NoDigitsFound);
|
|
}
|
|
let mut n: i64 = 0;
|
|
for &byte in bytes {
|
|
if !(b'0' <= byte && byte <= b'9') {
|
|
return Err(ParseIntError::InvalidDigit(byte));
|
|
}
|
|
let digit = i64::from(byte - b'0');
|
|
n = n
|
|
.checked_mul(10)
|
|
.and_then(|n| n.checked_add(digit))
|
|
.ok_or(ParseIntError::TooBig)?;
|
|
}
|
|
Ok(n)
|
|
}
|
|
|
|
/// Like `self::i64`, but also does a boundary check for the given type.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// If the given slice is not a valid integer (i.e., overflow or contains
|
|
/// anything other than `[0-9]`) or is not in the bounds for the given `Bounds`
|
|
/// implementation, then an error is returned.
|
|
///
|
|
/// Note that the error can either be a parsing error or it can be a
|
|
/// boundary error.
|
|
#[cfg_attr(feature = "perf-inline", inline(always))]
|
|
pub(crate) fn bi64<B>(bytes: &[u8]) -> Result<B::Primitive, Error>
|
|
where
|
|
B: Bounds,
|
|
Error: From<B::Error>,
|
|
{
|
|
Ok(B::check(self::i64(bytes)?)?)
|
|
}
|
|
|
|
/// Parsed an optional `u64` that is a prefix of `bytes`.
|
|
///
|
|
/// If no digits (`[0-9]`) were found at the beginning of `bytes`, then `None`
|
|
/// is returned.
|
|
///
|
|
/// Note that this is safe to call on untrusted input. It will not attempt
|
|
/// to consume more input than could possibly fit into a parsed integer.
|
|
///
|
|
/// Since this returns a `u64`, it is possible that an integer that cannot
|
|
/// fit into an `i64` is returned. Callers should handle this. (Indeed,
|
|
/// `DurationUnits` handles this case.)
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// When the parsed integer cannot fit into a `u64`.
|
|
#[cfg_attr(feature = "perf-inline", inline(always))]
|
|
pub(crate) fn u64_prefix(
|
|
bytes: &[u8],
|
|
) -> Result<(Option<u64>, &[u8]), ParseIntError> {
|
|
// Discovered via `u64::MAX.to_string().len()`.
|
|
const MAX_U64_DIGITS: usize = 20;
|
|
|
|
let mut digit_count = 0;
|
|
let mut n: u64 = 0;
|
|
while digit_count <= MAX_U64_DIGITS {
|
|
let Some(&byte) = bytes.get(digit_count) else { break };
|
|
if !byte.is_ascii_digit() {
|
|
break;
|
|
}
|
|
digit_count += 1;
|
|
// OK because we confirmed `byte` is an ASCII digit.
|
|
let digit = u64::from(byte - b'0');
|
|
n = n
|
|
.checked_mul(10)
|
|
.and_then(|n| n.checked_add(digit))
|
|
.ok_or(ParseIntError::TooBig)?;
|
|
}
|
|
if digit_count == 0 {
|
|
return Ok((None, bytes));
|
|
}
|
|
Ok((Some(n), &bytes[digit_count..]))
|
|
}
|
|
|
|
/// Parses a `u32` fractional number from the beginning to the end of the given
|
|
/// slice of ASCII digit characters.
|
|
///
|
|
/// The fraction's maximum precision is always 9 digits. The returned integer
|
|
/// will always be in units of `10^{max_precision}`. For example, this
|
|
/// will parse a fractional amount of seconds with a maximum precision of
|
|
/// nanoseconds.
|
|
///
|
|
/// If any byte in the given slice is not `[0-9]`, then this returns an error.
|
|
/// Notably, this routine does not permit parsing a negative integer.
|
|
pub(crate) fn fraction(bytes: &[u8]) -> Result<u32, ParseFractionError> {
|
|
if bytes.is_empty() {
|
|
return Err(ParseFractionError::NoDigitsFound);
|
|
} else if bytes.len() > ParseFractionError::MAX_PRECISION {
|
|
return Err(ParseFractionError::TooManyDigits);
|
|
}
|
|
let mut n: u32 = 0;
|
|
for &byte in bytes {
|
|
let digit = match byte.checked_sub(b'0') {
|
|
None => {
|
|
return Err(ParseFractionError::InvalidDigit(byte));
|
|
}
|
|
Some(digit) if digit > 9 => {
|
|
return Err(ParseFractionError::InvalidDigit(byte));
|
|
}
|
|
Some(digit) => {
|
|
debug_assert!((0..=9).contains(&digit));
|
|
u32::from(digit)
|
|
}
|
|
};
|
|
n = n
|
|
.checked_mul(10)
|
|
.and_then(|n| n.checked_add(digit))
|
|
.ok_or_else(|| ParseFractionError::TooBig)?;
|
|
}
|
|
for _ in bytes.len()..ParseFractionError::MAX_PRECISION {
|
|
n = n.checked_mul(10).ok_or_else(|| ParseFractionError::TooBig)?;
|
|
}
|
|
Ok(n)
|
|
}
|
|
|
|
/// Parses an `OsStr` into a `&str` when `&[u8]` isn't easily available.
|
|
///
|
|
/// This is effectively `OsStr::to_str`, but with a slightly better error
|
|
/// message.
|
|
#[cfg(any(feature = "tz-system", feature = "tzdb-zoneinfo"))]
|
|
pub(crate) fn os_str_utf8<'o, O>(
|
|
os_str: &'o O,
|
|
) -> Result<&'o str, crate::error::util::OsStrUtf8Error>
|
|
where
|
|
O: ?Sized + AsRef<std::ffi::OsStr>,
|
|
{
|
|
let os_str = os_str.as_ref();
|
|
os_str
|
|
.to_str()
|
|
.ok_or_else(|| crate::error::util::OsStrUtf8Error::from(os_str))
|
|
}
|
|
|
|
/// Splits the given input into two slices at the given position.
|
|
///
|
|
/// If the position is greater than the length of the slice given, then this
|
|
/// returns `None`.
|
|
#[cfg_attr(feature = "perf-inline", inline(always))]
|
|
pub(crate) fn split(input: &[u8], at: usize) -> Option<(&[u8], &[u8])> {
|
|
if at > input.len() {
|
|
None
|
|
} else {
|
|
Some(input.split_at(at))
|
|
}
|
|
}
|
|
|
|
/// Returns a function that converts two slices to an offset.
|
|
///
|
|
/// It takes the starting point as input and returns a function that, when
|
|
/// given an ending point (greater than or equal to the starting point), then
|
|
/// the corresponding pointers are subtracted and an offset relative to the
|
|
/// starting point is returned.
|
|
///
|
|
/// This is useful as a helper function in parsing routines that use slices
|
|
/// but want to report offsets.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This may panic if the ending point is not a suffix slice of `start`.
|
|
pub(crate) fn offseter<'a>(
|
|
start: &'a [u8],
|
|
) -> impl Fn(&'a [u8]) -> usize + 'a {
|
|
move |end| (end.as_ptr() as usize) - (start.as_ptr() as usize)
|
|
}
|
|
|
|
/// Returns a function that converts two slices to the slice between them.
|
|
///
|
|
/// This takes a starting point as input and returns a function that, when
|
|
/// given an ending point (greater than or equal to the starting point), it
|
|
/// returns a slice beginning at the starting point and ending just at the
|
|
/// ending point.
|
|
///
|
|
/// This is useful as a helper function in parsing routines.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This may panic if the ending point is not a suffix slice of `start`.
|
|
pub(crate) fn slicer<'a>(
|
|
start: &'a [u8],
|
|
) -> impl Fn(&'a [u8]) -> &'a [u8] + 'a {
|
|
let mkoffset = offseter(start);
|
|
move |end| {
|
|
let offset = mkoffset(end);
|
|
&start[..offset]
|
|
}
|
|
}
|