Skip to content

Instantly share code, notes, and snippets.

@thomcc
Created November 9, 2024 08:14
Show Gist options
  • Save thomcc/2230b4bf4ba0180afe5108406dd84a47 to your computer and use it in GitHub Desktop.
Save thomcc/2230b4bf4ba0180afe5108406dd84a47 to your computer and use it in GitHub Desktop.
//! From a library I never finished. I should still make it it's own crate TBH.
macro_rules! parse_env {
($var_name:literal as $typ:ident) => {{
const {
match $crate::parse_env::dispatch::$typ(::core::env!($var_name).as_bytes(), ::core::None) {
::core::Some(v) => v,
::core::None => {
::core::panic!(::core::concat!(
"error: the value in ",
::core::stringify!($s),
" doesn't parse as a number, or is out of range for `",
::core::stringify!($typ),
"`.",
));
}
}
}
}};
($var_name:literal as $typ:ident in $range:expr) => {{
const {
match $crate::parse_env::parse_bounded::$typ(
::core::env!($var_name).as_bytes(),
::core::None,
::core::Some($crate::parse_env::RangeWrap($range, ::core::marker::PhantomData<$typ>).start()),
::core::Some($crate::parse_env::RangeWrap($range, ::core::marker::PhantomData<$typ>).end_incl()),
) {
::core::Some(v) => v,
::core::None => {
::core::panic!(::core::concat!(
"error: the value in ",
::core::stringify!($s),
" doesn't parse as a number (`",
::core::stringify!($typ),
"`), or is out of the range`",
::core::stringify!($range),
"`."
));
}
}
}
}};
($var_name:literal as $typ:ident else $default:expr) => {{
const {
const __ENVPARSE_DEFAULT: $typ = $default;
match ::core::option_env!($var_name) {
::core::None => __ENVPARSE_DEFAULT,
::core::Some(s) => {
match $crate::parse_env::dispatch::$typ(
s.as_bytes(),
::core::Some(__ENVPARSE_DEFAULT),
) {
::core::Some(v) => v,
::core::None => {
::core::panic!(::core::concat!(
"error: the value in ",
::core::stringify!($s),
" doesn't parse as a number, or is out of range for `",
::core::stringify!($typ),
"`."
));
}
}
}
}
}
}};
($var_name:literal as $typ:ident (in $range:expr) else $default:expr) => {{
const {
const __ENVPARSE_DEFAULT: $typ = $default;
match ::core::option_env!($var_name) {
::core::None => __ENVPARSE_DEFAULT,
::core::Some(s) => {
match $crate::parse_env::parse_bounded::$typ(
s.as_bytes(),
::core::Some(__ENVPARSE_DEFAULT),
::core::Some($crate::parse_env::RangeWrap($range, ::core::marker::PhantomData<$typ>).start()),
::core::Some($crate::parse_env::RangeWrap($range, ::core::marker::PhantomData<$typ>).end_incl()),
) {
::core::Some(v) => v,
::core::None => {
::core::panic!(::core::concat!(
"error: the value in ",
::core::stringify!($s),
" doesn't parse as a number (`",
::core::stringify!($typ),
"`), or is out of the range`",
::core::stringify!($range),
"`."
));
}
}
}
}
}
}};
(try $var_name:literal as $typ:ident) => {{
const {
match ::core::option_env!($var_name) {
::core::None => ::core::None,
::core::Some(s) if s.is_empty() => ::core::None,
::core::Some(s) => {
match $crate::parse_env::dispatch::$typ(s.as_bytes(), ::core::None) {
::core::Some(v) => v,
::core::None => {
::core::panic!(::core::concat!(
"error: the value in ",
::core::stringify!($s),
" doesn't parse as a number, or is out of range for `",
::core::stringify!($typ),
"`."
));
}
}
}
}
}
}};
(try $var_name:literal as $typ:ident in $range:expr) => {{
const {
match ::core::option_env!($var_name) {
::core::None => ::core::None,
::core::Some(s) if s.is_empty() => ::core::None,
::core::Some(s) => {
match $crate::parse_env::parse_bounded::$typ(
s.as_bytes(),
::core::None,
::core::Some($crate::parse_env::RangeWrap($range, ::core::marker::PhantomData<$typ>).start()),
::core::Some($crate::parse_env::RangeWrap($range, ::core::marker::PhantomData<$typ>).end_incl()),
) {
::core::Some(v) => v,
::core::None => {
::core::panic!(::core::concat!(
"error: the value in ",
::core::stringify!($s),
" doesn't parse as a number (`",
::core::stringify!($typ),
"`), or is out of the range`",
::core::stringify!($range),
"`.",
));
}
}
}
}
}
}};
}
pub(crate) use parse_env;
#[derive(Copy, Clone)]
pub(crate) struct RangeWrap<R, T>(pub(crate) R, core::marker::PhantomData<T>);
macro_rules! def_to_inclusive {
($t:ident, $min:expr) => {
impl RangeWrap<core::ops::Range<$t>, $t> {
pub(crate) const fn start(&self) -> $t {
self.0.start
}
pub(crate) const fn end_incl(&self) -> $t {
self.0.end - 1
}
}
impl RangeWrap<core::ops::RangeFrom<$t>, $t> {
pub(crate) const fn start(&self) -> $t {
self.0.start
}
pub(crate) const fn end_incl(&self) -> $t {
$t::MAX
}
}
impl RangeWrap<core::ops::RangeTo<$t>, $t> {
pub(crate) const fn start(&self) -> $t {
$min
}
pub(crate) const fn end_incl(&self) -> $t {
self.0.end - 1
}
}
impl RangeWrap<core::ops::RangeInclusive<$t>, $t> {
pub(crate) const fn start(&self) -> $t {
*self.0.start()
}
pub(crate) const fn end_incl(&self) -> $t {
*self.0.end()
}
}
impl RangeWrap<core::ops::RangeToInclusive<$t>, $t> {
pub(crate) const fn start(&self) -> $t {
$min
}
pub(crate) const fn end_incl(&self) -> $t {
self.0.end
}
}
impl RangeWrap<core::ops::RangeFull, $t> {
pub(crate) const fn start(&self) -> $t {
$min
}
pub(crate) const fn end_incl(&self) -> $t {
$t::MAX
}
}
};
}
def_to_inclusive!(u8, 0);
def_to_inclusive!(u16, 0);
def_to_inclusive!(u32, 0);
def_to_inclusive!(u64, 0);
def_to_inclusive!(u128, 0);
def_to_inclusive!(usize, 0);
def_to_inclusive!(i8, i8::MIN);
def_to_inclusive!(i16, i16::MIN);
def_to_inclusive!(i32, i32::MIN);
def_to_inclusive!(i64, i64::MIN);
def_to_inclusive!(i128, i128::MIN);
def_to_inclusive!(isize, isize::MIN);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum ParseError {
Empty,
UnexpectedSign,
InvalidDigit,
NoDigits,
IntOverflow,
OutOfRange,
UnknownBoolValue,
}
pub(crate) const fn number_parse(s: &[u8], skip_sign: bool) -> Result<(u128, bool), ParseError> {
let (mut pos, end) = match trim_ws(s) {
Some((start, end)) => (start, end),
None => return Err(ParseError::Empty),
};
let neg = match s[pos] {
b'-' if !skip_sign => return Err(ParseError::UnexpectedSign),
c @ b'-' | c @ b'+' => {
pos += 1;
c == b'-'
}
_ => false,
};
if pos == end {
return Err(ParseError::NoDigits);
}
let radix = if pos + 2 <= end {
let (radix, len) = match (s[pos], s[pos + 1]) {
(b'0', b'x') | (b'0', b'X') => (16, 2),
(b'0', b'o') | (b'0', b'O') => (8, 2),
(b'0', b'b') | (b'0', b'B') => (2, 2),
_ => (10, 0),
};
pos += len;
radix
} else {
10
};
let mut accum = 0u128;
let mut ever_saw_digits = false;
while pos < end {
let d = s[pos];
pos += 1;
let value = match (d, radix) {
(b'0'..=b'1', 2) | (b'0'..=b'7', 8) | (b'0'..=b'9', 10 | 16) => (d - b'0') as u128,
(b'a'..=b'f', 16) => (d - b'a') as u128 + 10,
(b'A'..=b'F', 16) => (d - b'A') as u128 + 10,
(b'_', _) => continue,
_ => return Err(ParseError::InvalidDigit),
};
ever_saw_digits = true;
match accum.checked_mul(radix) {
None => return Err(ParseError::IntOverflow),
Some(shift) => match shift.checked_add(value) {
None => return Err(ParseError::IntOverflow),
Some(val) => accum = val,
},
}
}
if ever_saw_digits {
Ok((accum, neg))
} else {
Err(ParseError::NoDigits)
}
}
const fn trim_ws(s: &[u8]) -> Option<(usize, usize)> {
let mut start = 0;
if s.is_empty() || s.len() <= start {
return None;
}
while start < s.len() && s[start].is_ascii_whitespace() {
start += 1;
}
if start == s.len() {
return None;
}
let mut end = s.len() - 1;
while end > start && s[end].is_ascii_whitespace() {
end -= 1;
}
end += 1;
if end <= start {
None
} else {
Some((start, end))
}
}
pub(crate) const fn parse_unsigned(
s: &[u8],
incl_min: u128,
incl_max: u128,
clamp: bool,
) -> Result<u128, ParseError> {
let val = match number_parse(s, false) {
Ok((n, _)) => n,
Err(e) => match e {
ParseError::IntOverflow if clamp => incl_max,
ParseError::UnexpectedSign if clamp => incl_min,
e => return Err(e),
},
};
if val < incl_min {
return if clamp {
Ok(incl_min)
} else {
Err(ParseError::OutOfRange)
};
}
if val > incl_max {
return if clamp {
Ok(incl_max)
} else {
Err(ParseError::OutOfRange)
};
}
Ok(val)
}
pub(crate) const fn parse_signed(
s: &[u8],
incl_min: i128,
incl_max: i128,
clamp: bool,
) -> Result<i128, ParseError> {
const I128_MIN_MAGNITUDE: u128 = (i128::MAX as u128) + 1;
let val = match number_parse(s, true) {
Ok((n, true)) if n == I128_MIN_MAGNITUDE => i128::MIN,
Ok((n, true)) if n <= (i128::MAX as u128) => -(n as i128),
Ok((_, true)) if clamp => incl_min,
Ok((n, false)) if n <= (i128::MAX as u128) => n as i128,
Ok((_, false)) if clamp => incl_max,
Ok((_, _)) => return Err(ParseError::OutOfRange),
Err(e) => return Err(e),
};
if val < incl_min {
return if clamp {
Ok(incl_min)
} else {
Err(ParseError::OutOfRange)
};
}
if val > incl_max {
return if clamp {
Ok(incl_max)
} else {
Err(ParseError::OutOfRange)
};
}
Ok(val)
}
pub(crate) const fn parse_bool(s: &[u8]) -> Result<bool, ParseError> {
let (i, e) = match trim_ws(s) {
Some(tup) => tup,
None => return Err(ParseError::Empty),
};
const fn icase(c: u8) -> u8 {
c.to_ascii_lowercase()
}
let len = e.saturating_sub(i);
match len {
0 => Err(ParseError::Empty),
1 => match s[i].to_ascii_lowercase() {
b'1' | b't' | b'y' => Ok(true),
b'0' | b'f' | b'n' => Ok(false),
_ => Err(ParseError::UnknownBoolValue),
},
2 => match (icase(s[i]), icase(s[i + 1])) {
(b'n', b'o') => Ok(false),
(b'o', b'n') => Ok(true),
_ => Err(ParseError::UnknownBoolValue),
},
3 => match (icase(s[i]), icase(s[i + 1]), icase(s[i + 2])) {
(b'o', b'f', b'f') => Ok(false),
(b'y', b'e', b's') => Ok(true),
_ => Err(ParseError::UnknownBoolValue),
},
4 => match (
icase(s[i]),
icase(s[i + 1]),
icase(s[i + 2]),
icase(s[i + 3]),
) {
(b't', b'r', b'u', b'e') => Ok(true),
_ => Err(ParseError::UnknownBoolValue),
},
5 => match (
icase(s[i]),
icase(s[i + 1]),
icase(s[i + 2]),
icase(s[i + 3]),
icase(s[i + 4]),
) {
(b'f', b'a', b'l', b's', b'e') => Ok(false),
_ => Err(ParseError::UnknownBoolValue),
},
_ => Err(ParseError::UnknownBoolValue),
}
}
macro_rules! unwrap_or {
($o:expr, $or:expr) => {
match $o {
Some(inner) => inner,
None => $or,
}
};
}
mod parse_bounded {
use super::{parse_signed, parse_unsigned, ParseError::Empty};
// unsigned
pub(crate) const fn usize(
s: &[u8],
default: Option<usize>,
min: Option<usize>,
max: Option<usize>,
clamp: bool,
) -> Option<usize> {
match parse_unsigned(
s,
unwrap_or!(min, 0) as u128,
unwrap_or!(max, usize::MAX) as u128,
clamp,
) {
Ok(v) => Some(v as usize),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn u8(
s: &[u8],
default: Option<u8>,
min: Option<u8>,
max: Option<u8>,
clamp: bool,
) -> Option<u8> {
match parse_unsigned(
s,
unwrap_or!(min, 0) as u128,
unwrap_or!(max, u8::MAX) as u128,
clamp,
) {
Ok(v) => Some(v as u8),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn u16(
s: &[u8],
default: Option<u16>,
min: Option<u16>,
max: Option<u16>,
clamp: bool,
) -> Option<u16> {
match parse_unsigned(
s,
unwrap_or!(min, 0) as u128,
unwrap_or!(max, u16::MAX) as u128,
clamp,
) {
Ok(v) => Some(v as u16),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn u32(
s: &[u8],
default: Option<u32>,
min: Option<u32>,
max: Option<u32>,
clamp: bool,
) -> Option<u32> {
match parse_unsigned(
s,
unwrap_or!(min, 0) as u128,
unwrap_or!(max, u32::MAX) as u128,
clamp,
) {
Ok(v) => Some(v as u32),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn u64(
s: &[u8],
default: Option<u64>,
min: Option<u64>,
max: Option<u64>,
clamp: bool,
) -> Option<u64> {
match parse_unsigned(
s,
unwrap_or!(min, 0) as u128,
unwrap_or!(max, u64::MAX) as u128,
clamp,
) {
Ok(v) => Some(v as u64),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn u128(
s: &[u8],
default: Option<u128>,
min: Option<u128>,
max: Option<u128>,
clamp: bool,
) -> Option<u128> {
match parse_unsigned(s, unwrap_or!(min, 0), unwrap_or!(max, u128::MAX), clamp) {
Ok(v) => Some(v),
Err(Empty) => default,
_ => None,
}
}
// signed
pub(crate) const fn isize(
s: &[u8],
default: Option<isize>,
min: Option<isize>,
max: Option<isize>,
clamp: bool,
) -> Option<isize> {
match parse_signed(
s,
unwrap_or!(min, isize::MIN) as i128,
unwrap_or!(max, isize::MAX) as i128,
clamp,
) {
Ok(v) => Some(v as isize),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn i8(
s: &[u8],
default: Option<i8>,
min: Option<i8>,
max: Option<i8>,
clamp: bool,
) -> Option<i8> {
match parse_signed(
s,
unwrap_or!(min, i8::MIN) as i128,
unwrap_or!(max, i8::MAX) as i128,
clamp,
) {
Ok(v) => Some(v as i8),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn i16(
s: &[u8],
default: Option<i16>,
min: Option<i16>,
max: Option<i16>,
clamp: bool,
) -> Option<i16> {
match parse_signed(
s,
unwrap_or!(min, i16::MIN) as i128,
unwrap_or!(max, i16::MAX) as i128,
clamp,
) {
Ok(v) => Some(v as i16),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn i32(
s: &[u8],
default: Option<i32>,
min: Option<i32>,
max: Option<i32>,
clamp: bool,
) -> Option<i32> {
match parse_signed(
s,
unwrap_or!(min, i32::MIN) as i128,
unwrap_or!(max, i32::MAX) as i128,
clamp,
) {
Ok(v) => Some(v as i32),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn i64(
s: &[u8],
default: Option<i64>,
min: Option<i64>,
max: Option<i64>,
clamp: bool,
) -> Option<i64> {
match parse_signed(
s,
unwrap_or!(min, i64::MIN) as i128,
unwrap_or!(max, i64::MAX) as i128,
clamp,
) {
Ok(v) => Some(v as i64),
Err(Empty) => default,
_ => None,
}
}
pub(crate) const fn i128(
s: &[u8],
default: Option<i128>,
min: Option<i128>,
max: Option<i128>,
clamp: bool,
) -> Option<i128> {
match parse_signed(
s,
unwrap_or!(min, i128::MIN) as i128,
unwrap_or!(max, i128::MAX) as i128,
clamp,
) {
Ok(v) => Some(v as i128),
Err(Empty) => default,
_ => None,
}
}
}
pub(crate) mod dispatch {
use super::ParseError::Empty;
pub(crate) const fn usize(s: &[u8], default: Option<usize>) -> Option<usize> {
super::parse_bounded::usize(s, default, None, None, false)
}
pub(crate) const fn u8(s: &[u8], default: Option<u8>) -> Option<u8> {
super::parse_bounded::u8(s, default, None, None, false)
}
pub(crate) const fn u16(s: &[u8], default: Option<u16>) -> Option<u16> {
super::parse_bounded::u16(s, default, None, None, false)
}
pub(crate) const fn u32(s: &[u8], default: Option<u32>) -> Option<u32> {
super::parse_bounded::u32(s, default, None, None, false)
}
pub(crate) const fn u64(s: &[u8], default: Option<u64>) -> Option<u64> {
super::parse_bounded::u64(s, default, None, None, false)
}
pub(crate) const fn u128(s: &[u8], default: Option<u128>) -> Option<u128> {
super::parse_bounded::u128(s, default, None, None, false)
}
pub(crate) const fn isize(s: &[u8], default: Option<isize>) -> Option<isize> {
super::parse_bounded::isize(s, default, None, None, false)
}
pub(crate) const fn i8(s: &[u8], default: Option<i8>) -> Option<i8> {
super::parse_bounded::i8(s, default, None, None, false)
}
pub(crate) const fn i16(s: &[u8], default: Option<i16>) -> Option<i16> {
super::parse_bounded::i16(s, default, None, None, false)
}
pub(crate) const fn i32(s: &[u8], default: Option<i32>) -> Option<i32> {
super::parse_bounded::i32(s, default, None, None, false)
}
pub(crate) const fn i64(s: &[u8], default: Option<i64>) -> Option<i64> {
super::parse_bounded::i64(s, default, None, None, false)
}
pub(crate) const fn i128(s: &[u8], default: Option<i128>) -> Option<i128> {
super::parse_bounded::i128(s, default, None, None, false)
}
pub(crate) const fn bool(s: &[u8], default: Option<bool>) -> Option<bool> {
match super::parse_bool(s) {
Ok(v) => Some(v),
Err(Empty) => default,
_ => None,
}
}
}
#[cfg(test)]
mod test {
extern crate alloc;
use super::*;
use ParseError::*;
#[test]
fn test_trim_empty() {
assert_eq!(trim_ws(b""), None);
assert_eq!(trim_ws(b" \t\n\r"), None);
assert_eq!(trim_ws(b" \t\n\r"), None);
for i in 0..15 {
for c in [" ", "\t", "\n", "\r"] {
let s = c.repeat(i);
assert_eq!(
trim_ws(s.as_bytes()),
None,
"string of {} spaces (type = {:?}): {:?}",
s.len(),
c,
s,
);
for c2 in [" ", "\t", "\n", "\r"] {
let cc = alloc::format!("{}{}", c, c2);
let s2 = cc.repeat(i);
assert_eq!(
trim_ws(s.as_bytes()),
None,
"string of {} spaces (type = {:?}): {:?}",
s2.len(),
cc,
s2,
);
}
}
}
}
#[test]
fn test_trim() {
fn check(s: &str, r: core::ops::Range<usize>) {
assert_eq!(
trim_ws(s.as_bytes()),
Some((r.start, r.end)),
"trim {:?}",
(s, r)
);
let (sr, se) = trim_ws(s.as_bytes()).unwrap();
assert_eq!(
s.get(sr..se),
Some(s.trim()),
"trim smoke {:?}",
(s, r, sr..se),
);
}
for i in 1..10 {
let s = "a".repeat(i);
let r = 0..s.len();
check(&s, r.clone());
for i in 0..15 {
for p1 in [" ", "\t", "\r", "\n"] {
for p2 in [" ", "\t", "\r", "\n"] {
let pad = alloc::format!("{}{}", p1, p2).repeat(i);
check(&alloc::format!("{}{}", s, pad), r.clone());
let padded_r = r.start + pad.len()..r.end + pad.len();
check(&alloc::format!("{}{}", pad, s), padded_r.clone());
check(&alloc::format!("{}{}{}", pad, s, pad), padded_r.clone());
}
}
}
}
}
fn mixcase(s: &str, b: bool) -> alloc::string::String {
s.chars()
.enumerate()
.map(|(i, c)| {
if (i % 2 == 0) ^ b {
c.to_uppercase().collect::<alloc::string::String>()
} else {
c.to_lowercase().collect::<alloc::string::String>()
}
})
.collect()
}
#[test]
fn test_parse_unsigned() {
#[track_caller]
fn check(s: &str, res: Result<u128, ParseError>) {
assert_eq!(
parse_unsigned(s.as_ref(), 0, u128::MAX, false),
res,
"input: {:?}",
(s, res),
);
}
#[track_caller]
fn ok1(s: &str, v: u128) {
check(s, Ok(v));
}
#[track_caller]
fn err1(s: &str, e: ParseError) {
check(s, Err(e))
}
#[track_caller]
fn ok(s: &str, v: u128) {
ok1(s, v);
ok1(&s.to_uppercase(), v);
ok1(&s.to_lowercase(), v);
ok1(&mixcase(s, true), v);
ok1(&mixcase(s, false), v);
ok1(&alloc::format!(" {}", s), v);
ok1(&alloc::format!("{} ", s), v);
ok1(&alloc::format!(" {} ", s), v);
ok1(&alloc::format!("{} ", s), v);
ok1(&alloc::format!(" {}", s), v);
ok1(&alloc::format!(" {} ", s), v);
if !s.contains("+") {
ok1(&alloc::format!("+{}", s), v);
ok1(&alloc::format!("+{} ", s), v);
ok1(&alloc::format!(" +{}", s), v);
ok1(&alloc::format!(" +{} ", s), v);
ok1(&alloc::format!("+{} ", s), v);
ok1(&alloc::format!(" +{}", s), v);
ok1(&alloc::format!(" +{} ", s), v);
}
}
#[track_caller]
fn err(s: &str, e: ParseError) {
err1(s, e);
err1(&s.to_uppercase(), e);
err1(&s.to_lowercase(), e);
err1(&alloc::format!("{} ", s), e);
err1(&alloc::format!(" {}", s), e);
err1(&alloc::format!(" {} ", s), e);
err1(&alloc::format!("{} ", s), e);
err1(&alloc::format!(" {}", s), e);
err1(&alloc::format!(" {} ", s), e);
if !s.contains("+") && !s.contains("-") && !s.is_empty() {
err1(&alloc::format!("+{}", s), e);
err1(&alloc::format!("+{} ", s), e);
err1(&alloc::format!(" +{}", s), e);
err1(&alloc::format!(" +{} ", s), e);
err1(&alloc::format!("+{} ", s), e);
err1(&alloc::format!(" +{}", s), e);
err1(&alloc::format!(" +{} ", s), e);
}
}
ok("0x1234abcd", 0x1234abcd);
ok("0x__12_34__a__b__c__d__", 0x1234abcd);
ok("1234567890", 1234567890);
ok("0o12345670", 0o12345670);
ok("0b101010", 0b101010);
ok("0xabcdef0123456789", 0xabcdef0123456789);
ok("0o3777777777777777777777777777777777777777777", u128::MAX);
ok("0xffffffffffffffffffffffffffffffff", u128::MAX);
ok("0Xffffffffffffffffffffffffffffffff", u128::MAX);
ok("340282366920938463463374607431768211455", u128::MAX);
ok("0o3777777777777777777777777777777777777777777", u128::MAX);
ok("0xffffffffffffffffffffffffffffffff", u128::MAX);
ok("0Xffffffffffffffffffffffffffffffff", u128::MAX);
ok(
"0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
u128::MAX,
);
ok("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff", u128::MAX);
ok("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff___", u128::MAX);
ok(
"0x__f_f_f_f_ffff_ffff_ffff_ffff_ffff_ffff_ffff___",
u128::MAX,
);
err("", Empty);
err("-30", UnexpectedSign);
err("-", UnexpectedSign);
err("", Empty);
err("0x1234g", InvalidDigit);
err("0xg1234", InvalidDigit);
err("0x1234g1234", InvalidDigit);
err("123a", InvalidDigit);
err("a123", InvalidDigit);
err("123a123", InvalidDigit);
err("0o8", InvalidDigit);
err("0o128", InvalidDigit);
err("0o12812", InvalidDigit);
err("0o12a12", InvalidDigit);
err("0b2", InvalidDigit);
err("0b00200", InvalidDigit);
err("0b002", InvalidDigit);
err("0b200", InvalidDigit);
err("0b121", InvalidDigit);
err("0b12", InvalidDigit);
err("0b21", InvalidDigit);
err("0o", NoDigits);
err("0o_", NoDigits);
err("0o__", NoDigits);
err("0x", NoDigits);
err("0x_", NoDigits);
err("0x___", NoDigits);
err("0b", NoDigits);
err("0b_", NoDigits);
err("0b__", NoDigits);
err("_0b", InvalidDigit);
err("0o4000000000000000000000000000000000000000000", IntOverflow);
err("0xffffffffffffffffffffffffffffffff0", IntOverflow);
err("0xf0fffffffffffffffffffffffffffffff0", IntOverflow);
assert_eq!(parse_unsigned(b"200", 100, 1000, false), Ok(200));
assert_eq!(parse_unsigned(b"1000", 100, 1000, false), Ok(1000));
assert_eq!(parse_unsigned(b"100", 100, 1000, false), Ok(100));
assert_eq!(parse_unsigned(b"500", 600, 700, false), Err(OutOfRange),);
assert_eq!(parse_unsigned(b"500", 200, 300, false), Err(OutOfRange),);
assert_eq!(parse_unsigned(b"500", 600, 700, true), Ok(600));
assert_eq!(parse_unsigned(b"500", 200, 300, true), Ok(300));
assert_eq!(parse_unsigned(b"250", 200, 300, true), Ok(250));
assert_eq!(parse_unsigned(b"500", 200, u128::MAX, true), Ok(500));
assert_eq!(parse_unsigned(b"250", 0, 300, true), Ok(250));
assert_eq!(parse_unsigned(b"0", 0, 200, true), Ok(0));
assert_eq!(parse_unsigned(b"-1", 0, 200, true), Ok(0));
assert_eq!(parse_unsigned(b"0", 1, 255, true), Ok(1));
assert_eq!(parse_unsigned(b"-1", 1, 255, true), Ok(1));
assert_eq!(parse_unsigned(b"0", 0, u128::MAX, true), Ok(0));
assert_eq!(parse_unsigned(b"-1", 0, u128::MAX, true), Ok(0));
assert_eq!(parse_unsigned(b"-1", 0, u128::MAX, true), Ok(0));
assert_eq!(parse_unsigned(b"1000", 1, 255, false), Err(OutOfRange));
assert_eq!(parse_unsigned(b"1000", 1, 255, true), Ok(255));
assert_eq!(
parse_unsigned(
b"0o4000000000000000000000000000000000000000000",
0,
u128::MAX,
true,
),
Ok(u128::MAX),
);
assert_eq!(
parse_unsigned(b"0xffffffffffffffffffffffffffffffff0", 0, u128::MAX, true),
Ok(u128::MAX),
);
assert_eq!(
parse_unsigned(b"0xf0fffffffffffffffffffffffffffffff0", 0, u128::MAX, true),
Ok(u128::MAX),
);
assert_eq!(
parse_unsigned(
b"0o4000000000000000000000000000000000000000000",
0,
50,
true,
),
Ok(50),
);
assert_eq!(
parse_unsigned(b"0xffffffffffffffffffffffffffffffff0", 0, 50, true),
Ok(50),
);
assert_eq!(
parse_unsigned(b"0xf0fffffffffffffffffffffffffffffff0", 0, 50, true,),
Ok(50),
);
}
#[test]
fn test_parse_signed() {
#[track_caller]
fn check(s: &str, res: Result<i128, ParseError>) {
assert_eq!(
parse_signed(s.as_ref(), i128::MIN, i128::MAX, false),
res,
"input: {:?}",
(s, res),
);
}
#[track_caller]
fn ok1(s: &str, v: i128) {
check(s, Ok(v));
}
#[track_caller]
fn err1(s: &str, e: ParseError) {
check(s, Err(e))
}
#[track_caller]
fn ok(s: &str, v: i128) {
ok1(s, v);
ok1(&s.to_uppercase(), v);
ok1(&s.to_lowercase(), v);
ok1(&mixcase(s, true), v);
ok1(&mixcase(s, false), v);
ok1(&alloc::format!(" {}", s), v);
ok1(&alloc::format!("{} ", s), v);
ok1(&alloc::format!(" {} ", s), v);
ok1(&alloc::format!("{} ", s), v);
ok1(&alloc::format!(" {}", s), v);
ok1(&alloc::format!(" {} ", s), v);
if !s.contains("+") && !s.contains("-") {
assert!(v >= 0, "bug in test {:?}", (s, v));
ok1(&alloc::format!("+{}", s), v);
ok1(&alloc::format!("+{} ", s), v);
ok1(&alloc::format!(" +{}", s), v);
ok1(&alloc::format!(" +{} ", s), v);
ok1(&alloc::format!("+{} ", s), v);
ok1(&alloc::format!(" +{}", s), v);
ok1(&alloc::format!(" +{} ", s), v);
if let Some(n) = v.checked_neg() {
ok1(&alloc::format!("-{} ", s), n);
ok1(&alloc::format!("-{} ", s), n);
ok1(&alloc::format!(" -{}", s), n);
ok1(&alloc::format!(" -{} ", s), n);
ok1(&alloc::format!("-{} ", s), n);
ok1(&alloc::format!(" -{}", s), n);
ok1(&alloc::format!(" -{} ", s), n);
}
}
}
#[track_caller]
fn err(s: &str, e: ParseError) {
err1(s, e);
err1(&s.to_uppercase(), e);
err1(&s.to_lowercase(), e);
err1(&alloc::format!("{} ", s), e);
err1(&alloc::format!(" {}", s), e);
err1(&alloc::format!(" {} ", s), e);
err1(&alloc::format!("{} ", s), e);
err1(&alloc::format!(" {}", s), e);
err1(&alloc::format!(" {} ", s), e);
if !s.contains("+") && !s.contains("-") && !s.is_empty() {
err1(&alloc::format!("+{} ", s), e);
err1(&alloc::format!(" +{}", s), e);
err1(&alloc::format!(" +{} ", s), e);
err1(&alloc::format!("+{} ", s), e);
err1(&alloc::format!(" +{}", s), e);
err1(&alloc::format!(" +{} ", s), e);
}
}
ok("0", 0);
ok("1", 1);
ok("100", 100);
ok("0o0", 0);
ok("0o__0__", 0);
ok("0x1234abcd", 0x1234abcd);
ok("0x__12_34__a__b__c__d__", 0x1234abcd);
ok("170141183460469231731687303715884105727", i128::MAX);
ok("-170141183460469231731687303715884105728", i128::MIN);
ok("0x7fffffffffffffffffffffffffffffff", i128::MAX);
ok("-0x80000000000000000000000000000000", i128::MIN);
ok(
"0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
i128::MAX,
);
ok(
"-0b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
i128::MIN,
);
ok("0o1777777777777777777777777777777777777777777", i128::MAX);
ok("-0o2000000000000000000000000000000000000000000", i128::MIN);
ok("170141183460469231731687303715884105726", i128::MAX - 1);
ok("-170141183460469231731687303715884105727", i128::MIN + 1);
ok("-1234567890", -1234567890);
ok("-0o12345670", -0o12345670);
ok("-0b101010", -0b101010);
ok("-0xabcdef0123456789", -0xabcdef0123456789);
err("170141183460469231731687303715884105728", OutOfRange);
err("1701411834604692317316873037158841057270", IntOverflow);
err("", Empty);
err("-", NoDigits);
err("0x1234z", InvalidDigit);
err("123f", InvalidDigit);
err("0x1234z", InvalidDigit);
err("123f", InvalidDigit);
err("0o", NoDigits);
err("0o_", NoDigits);
err("0o__", NoDigits);
err("0x", NoDigits);
err("0x_", NoDigits);
err("0x___", NoDigits);
err("0b", NoDigits);
err("0b_", NoDigits);
err("0b__", NoDigits);
err("_0b", InvalidDigit);
err("0o4000000000000000000000000000000000000000000", IntOverflow);
err("0x7fffffffffffffffffffffffffffffff0", IntOverflow);
err("0x70fffffffffffffffffffffffffffffff0", IntOverflow);
err("0o3777777777777777777777777777777777777777777", OutOfRange);
err("0xffffffffffffffffffffffffffffffff", OutOfRange);
err(
"0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
OutOfRange,
);
err("170141183460469231731687303715884105728", OutOfRange);
err("-170141183460469231731687303715884105729", OutOfRange);
assert_eq!(parse_signed(b"0", -1000, 1000, false), Ok(0),);
assert_eq!(parse_signed(b"1000", -1000, 1000, false), Ok(1000),);
assert_eq!(parse_signed(b"-1000", -1000, 1000, false), Ok(-1000),);
assert_eq!(
parse_signed(b"1000", i128::MIN, 999, false),
Err(OutOfRange),
);
assert_eq!(parse_signed(b"1000", 999, i128::MAX, true), Ok(1000),);
assert_eq!(parse_signed(b"1000", i128::MIN, 999, true), Ok(999),);
assert_eq!(
parse_signed(b"-1000", -999, i128::MAX, false),
Err(OutOfRange),
);
assert_eq!(parse_signed(b"-1000", -999, i128::MAX, true), Ok(-999),);
assert_eq!(parse_signed(b"-1001", -1000, 1000, false), Err(OutOfRange),);
assert_eq!(parse_signed(b"1001", -1000, 1000, false), Err(OutOfRange),);
assert_eq!(parse_signed(b"-1001", -1000, 1000, true), Ok(-1000));
assert_eq!(parse_signed(b"1001", -1000, 1000, true), Ok(1000));
assert_eq!(parse_signed(b"1001", -1000, 1000, true), Ok(1000));
assert_eq!(parse_signed(b"500", 600, 700, true), Ok(600));
assert_eq!(parse_signed(b"500", 200, 300, true), Ok(300));
assert_eq!(parse_signed(b"500", 200, i128::MAX, true), Ok(500));
assert_eq!(parse_signed(b"250", 200, 300, true), Ok(250));
assert_eq!(parse_signed(b"500", 200, i128::MAX, true), Ok(500));
assert_eq!(parse_signed(b"250", i128::MIN, 300, true), Ok(250));
assert_eq!(parse_signed(b"150", -300, 200, true), Ok(150));
assert_eq!(parse_signed(b"-150", -300, 200, true), Ok(-150));
assert_eq!(parse_signed(b"300", -300, 200, true), Ok(200));
assert_eq!(parse_signed(b"-400", -300, 200, true), Ok(-300));
assert_eq!(parse_signed(b"-250", -300, -200, true), Ok(-250));
assert_eq!(parse_signed(b"-500", -200, i128::MAX, true), Ok(-200));
assert_eq!(parse_signed(b"-250", -300, i128::MAX, true), Ok(-250));
assert_eq!(parse_signed(b"0", i128::MIN, 200, true), Ok(0));
assert_eq!(parse_signed(b"-1", i128::MIN, 200, true), Ok(-1));
assert_eq!(parse_signed(b"0", 1, 255, true), Ok(1));
assert_eq!(parse_signed(b"-1", 1, 255, true), Ok(1));
assert_eq!(parse_signed(b"0", i128::MIN, i128::MAX, true), Ok(0));
assert_eq!(parse_signed(b"-1", i128::MIN, i128::MAX, true), Ok(-1));
assert_eq!(parse_signed(b"1", i128::MIN, i128::MAX, true), Ok(1));
assert_eq!(parse_signed(b"1000", 1, 255, false), Err(OutOfRange));
assert_eq!(parse_signed(b"-1000", 1, 255, false), Err(OutOfRange));
assert_eq!(parse_signed(b"-1000", -255, 255, false), Err(OutOfRange));
assert_eq!(parse_signed(b"1000", 1, 255, true), Ok(255));
assert_eq!(parse_signed(b"-1000", 1, 255, true), Ok(1));
assert_eq!(parse_signed(b"-1000", -255, 255, true), Ok(-255));
assert_eq!(parse_signed(b"1000", 1, 255, true), Ok(255));
assert_eq!(parse_signed(b"1000", -255, -1, true), Ok(-1));
assert_eq!(parse_signed(b"-1000", -255, -1, true), Ok(-255));
assert_eq!(
parse_signed(
b"0o3777777777777777777777777777777777777777777",
i128::MIN,
i128::MAX,
true
),
Ok(i128::MAX)
);
assert_eq!(
parse_signed(
b"0o3777777777777777777777777777777777777777777",
i128::MIN,
30,
true
),
Ok(30)
);
assert_eq!(
parse_signed(
b"0o3777777777777777777777777777777777777777777",
i128::MIN,
-30,
true
),
Ok(-30)
);
assert_eq!(
parse_signed(
b"0xffffffffffffffffffffffffffffffff",
i128::MIN,
i128::MAX,
true
),
Ok(i128::MAX)
);
assert_eq!(
parse_signed(b"0xffffffffffffffffffffffffffffffff", i128::MIN, 30, true),
Ok(30)
);
assert_eq!(
parse_signed(b"0xffffffffffffffffffffffffffffffff", i128::MIN, -30, true),
Ok(-30)
);
assert_eq!(parse_signed(b"0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", i128::MIN, i128::MAX, true), Ok(i128::MAX));
assert_eq!(parse_signed(b"0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", i128::MIN, 30, true), Ok(30));
assert_eq!(parse_signed(b"0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", i128::MIN, -30, true), Ok(-30));
assert_eq!(
parse_signed(
b"170141183460469231731687303715884105728",
i128::MIN,
i128::MAX,
true
),
Ok(i128::MAX)
);
assert_eq!(
parse_signed(
b"170141183460469231731687303715884105728",
i128::MIN,
30,
true
),
Ok(30)
);
assert_eq!(
parse_signed(
b"170141183460469231731687303715884105728",
i128::MIN,
-30,
true
),
Ok(-30)
);
assert_eq!(
parse_signed(
b"170141183460469231731687303715884105728",
10,
i128::MAX,
true
),
Ok(i128::MAX)
);
assert_eq!(
parse_signed(b"170141183460469231731687303715884105728", 10, 30, true),
Ok(30)
);
assert_eq!(
parse_signed(b"170141183460469231731687303715884105728", -30, 10, true),
Ok(10)
);
assert_eq!(
parse_signed(
b"-170141183460469231731687303715884105729",
i128::MIN,
i128::MAX,
true
),
Ok(i128::MIN)
);
assert_eq!(
parse_signed(
b"-170141183460469231731687303715884105729",
i128::MIN,
30,
true
),
Ok(i128::MIN)
);
assert_eq!(
parse_signed(
b"-170141183460469231731687303715884105729",
i128::MIN,
-30,
true
),
Ok(i128::MIN)
);
assert_eq!(
parse_signed(
b"-170141183460469231731687303715884105729",
i128::MIN,
10,
true
),
Ok(i128::MIN)
);
assert_eq!(
parse_signed(
b"-170141183460469231731687303715884105729",
30,
i128::MAX,
true
),
Ok(30)
);
assert_eq!(
parse_signed(
b"-170141183460469231731687303715884105729",
-30,
i128::MAX,
true
),
Ok(-30)
);
}
#[test]
fn test_parse_bool() {
#[track_caller]
fn check(s: &str, res: Result<bool, ParseError>) {
assert_eq!(parse_bool(s.as_ref()), res, "input: {:?}", (s, res),);
}
#[track_caller]
fn ok(s: &str, res: bool) {
check(s, Ok(res));
check(&alloc::format!("{} ", s), Ok(res));
check(&alloc::format!(" {}", s), Ok(res));
check(&alloc::format!(" {} ", s), Ok(res));
check(&s.to_lowercase(), Ok(res));
check(&s.to_uppercase(), Ok(res));
check(&mixcase(s, true), Ok(res));
check(&mixcase(s, false), Ok(res));
}
#[track_caller]
fn err(s: &str, e: ParseError) {
check(s, Err(e));
check(&alloc::format!("{} ", s), Err(e));
check(&alloc::format!(" {}", s), Err(e));
check(&alloc::format!(" {} ", s), Err(e));
check(&s.to_lowercase(), Err(e));
check(&s.to_uppercase(), Err(e));
check(&mixcase(s, true), Err(e));
check(&mixcase(s, false), Err(e));
}
ok("t", true);
ok("f", false);
ok("y", true);
ok("n", false);
ok("1", true);
ok("0", false);
ok("true", true);
ok("false", false);
ok("on", true);
ok("off", false);
ok("yes", true);
ok("no", false);
err("", Empty);
err("foo", UnknownBoolValue);
err("x", UnknownBoolValue);
err("01", UnknownBoolValue);
err("abc", UnknownBoolValue);
err("defg", UnknownBoolValue);
err("true1", UnknownBoolValue);
err("0true1", UnknownBoolValue);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment