#![warn(missing_docs)]
#![warn(missing_crate_level_docs)]
#![warn(private_doc_tests)]
#![warn(clippy::all)]
#![warn(clippy::cargo)]
#![forbid(unsafe_code)]
pub trait BStrParse {
fn parse<F: FromBStr>(&self) -> Result<F, F::Err>;
}
impl BStrParse for [u8] {
#[inline]
fn parse<F: FromBStr>(&self) -> Result<F, F::Err> {
FromBStr::from_bstr(self)
}
}
pub trait FromBStr: Sized {
type Err;
fn from_bstr(s: &[u8]) -> Result<Self, Self::Err>;
}
#[doc(hidden)]
trait FromBStrRadixHelper: PartialOrd + Copy {
fn min_value() -> Self;
fn max_value() -> Self;
fn from_u32(u: u32) -> Self;
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_sub(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
macro_rules! from_bstr_radix_int_impl {
($($t:ty)*) => {$(
impl FromBStr for $t {
type Err = ParseIntError;
#[inline]
fn from_bstr(src: &[u8]) -> Result<Self, ParseIntError> {
from_bstr_radix(src, 10)
}
}
)*}
}
from_bstr_radix_int_impl! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 }
macro_rules! doit {
($($t:ty)*) => ($(impl FromBStrRadixHelper for $t {
#[inline]
fn min_value() -> Self { Self::MIN }
#[inline]
fn max_value() -> Self { Self::MAX }
#[inline]
fn from_u32(u: u32) -> Self { u as Self }
#[inline]
fn checked_mul(&self, other: u32) -> Option<Self> {
Self::checked_mul(*self, other as Self)
}
#[inline]
fn checked_sub(&self, other: u32) -> Option<Self> {
Self::checked_sub(*self, other as Self)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
Self::checked_add(*self, other as Self)
}
})*)
}
doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
fn from_bstr_radix<T: FromBStrRadixHelper>(src: &[u8], radix: u32) -> Result<T, ParseIntError> {
use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!(
radix >= 2 && radix <= 36,
"from_bstr_radix_int: must lie in the range `[2, 36]` - found {}",
radix
);
if src.is_empty() {
return Err(PIE { kind: Empty });
}
let is_signed_ty = T::from_u32(0) > T::min_value();
let (is_positive, digits) = match src[0] {
b'+' => (true, &src[1..]),
b'-' if is_signed_ty => (false, &src[1..]),
_ => (true, src),
};
if digits.is_empty() {
return Err(PIE { kind: Empty });
}
let mut result = T::from_u32(0);
if is_positive {
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
result = match result.checked_add(x) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
}
} else {
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
result = match result.checked_sub(x) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
}
}
Ok(result)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIntError {
kind: IntErrorKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum IntErrorKind {
Empty,
InvalidDigit,
Overflow,
Underflow,
Zero,
}
impl ParseIntError {
pub fn kind(&self) -> &IntErrorKind {
&self.kind
}
#[doc(hidden)]
pub fn __description(&self) -> &str {
match self.kind {
IntErrorKind::Empty => "cannot parse integer from empty string",
IntErrorKind::InvalidDigit => "invalid digit found in string",
IntErrorKind::Overflow => "number too large to fit in target type",
IntErrorKind::Underflow => "number too small to fit in target type",
IntErrorKind::Zero => "number would be zero for non-zero type",
}
}
}
impl std::fmt::Display for ParseIntError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.__description().fmt(f)
}
}
impl std::error::Error for ParseIntError {
#[allow(deprecated)]
fn description(&self) -> &str {
self.__description()
}
}
pub trait FromBStrRadix: Sized {
fn from_bstr_radix(src: &[u8], radix: u32) -> Result<Self, ParseIntError>;
}
macro_rules! doc_comment {
($x:expr, $($tt:tt)*) => {
#[doc = $x]
$($tt)*
};
}
macro_rules! from_bstr_radix_impl {
($($t:ty)*) => {$(
impl FromBStrRadix for $t {
doc_comment! {
concat!("Converts a string slice in a given base to an integer.
The string is expected to be an optional `+` or `-` sign followed by digits.
Leading and trailing whitespace represent an error. Digits are a subset of these characters,
depending on `radix`:
* `0-9`
* `a-z`
* `A-Z`
# Panics
This function panics if `radix` is not in the range from 2 to 36.
# Examples
Basic usage:
```
use bstr_parse::*;
assert_eq!(", stringify!($t), "::from_bstr_radix(b\"A\", 16), Ok(10));
```"),
#[inline]
fn from_bstr_radix(src: &[u8], radix: u32) -> Result<Self, ParseIntError> {
from_bstr_radix(src, radix)
}
}
}
)*}
}
from_bstr_radix_impl! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }