use crate::prelude::*;
use winnow::token::take;
#[cfg(feature = "ascii")]
use winnow::ascii::{alpha1, alphanumeric1, dec_int, dec_uint, digit1, float, hex_uint};
#[cfg(feature = "ascii")]
use winnow::combinator::{eof, terminated};
#[inline(always)]
pub fn to_string_utf8(len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<String> {
move |input| {
take(len)
.map(|slice| String::from_utf8_lossy(slice).to_string())
.parse_next(input)
}
}
#[inline(always)]
pub fn to_string_utf8_strict(len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<String> {
move |input| {
take(len)
.try_map(|slice: &[u8]| core::str::from_utf8(slice).map(str::to_owned))
.context(winnow::error::StrContext::Label(
"Unable to decode bytes as UTF-8",
))
.parse_next(input)
}
}
#[inline(always)]
pub fn to_string_utf16_le(len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<String> {
move |input| {
if !len.is_multiple_of(2) {
let checkpoint = input.checkpoint();
return Err(crate::__export::labeled_error(
input,
&checkpoint,
"Invalid UTF-16 slice length",
));
}
take(len)
.map(|slice: &[u8]| {
let utf16: Vec<u16> = slice
.chunks_exact(2)
.map(|chunk| {
#[allow(
clippy::unwrap_used,
reason = "safe to unwrap, since `chunks_exact` returns exactly 2 bytes"
)]
let array: [u8; 2] = chunk.try_into().unwrap();
u16::from_le_bytes(array)
})
.collect();
String::from_utf16_lossy(&utf16)
})
.parse_next(input)
}
}
#[inline(always)]
pub fn to_string_utf16_be(len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<String> {
move |input| {
if !len.is_multiple_of(2) {
let checkpoint = input.checkpoint();
return Err(crate::__export::labeled_error(
input,
&checkpoint,
"Invalid UTF-16 slice length",
));
}
take(len)
.map(|slice: &[u8]| {
let utf16: Vec<u16> = slice
.chunks_exact(2)
.map(|chunk| {
#[allow(
clippy::unwrap_used,
reason = "safe to unwrap, since `chunks_exact` returns exactly 2 bytes"
)]
let array: [u8; 2] = chunk.try_into().unwrap();
u16::from_be_bytes(array)
})
.collect();
String::from_utf16_lossy(&utf16)
})
.parse_next(input)
}
}
#[cfg(feature = "ascii")]
#[inline(always)]
pub fn to_string_ascii(len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<String> {
move |input| {
take(len)
.verify(<[u8]>::is_ascii)
.map(|slice: &[u8]| String::from_utf8_lossy(slice).into_owned())
.context(winnow::error::StrContext::Label("Unable to decode bytes as ASCII"))
.parse_next(input)
}
}
macro_rules! ascii_uint {
($ty:ty) => {
pastey::paste! {
#[cfg(feature = "ascii")]
#[inline(always)]
#[doc = concat!("Decodes exactly `len` ASCII bytes as a base-10 [`", stringify!($ty), "`].")]
#[doc = ""]
#[doc = "The entire `len`-byte field must be the decimal numeral; any"]
#[doc = "trailing or non-digit byte is rejected."]
pub fn [<$ty>](len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<$ty> {
move |input| {
take(len)
.and_then(terminated(dec_uint::<_, $ty, _>, eof))
.parse_next(input)
}
}
}
};
}
macro_rules! ascii_int {
($ty:ty) => {
pastey::paste! {
#[cfg(feature = "ascii")]
#[inline(always)]
#[doc = concat!("Decodes exactly `len` ASCII bytes as a base-10 [`", stringify!($ty), "`].")]
#[doc = ""]
#[doc = "Accepts an optional leading `+`/`-` sign. The entire `len`-byte"]
#[doc = "field must be the numeral; any trailing byte is rejected."]
pub fn [<$ty>](len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<$ty> {
move |input| {
take(len)
.and_then(terminated(dec_int::<_, $ty, _>, eof))
.parse_next(input)
}
}
}
};
}
macro_rules! ascii_float {
($ty:ty) => {
pastey::paste! {
#[cfg(feature = "ascii")]
#[inline(always)]
#[doc = concat!("Decodes exactly `len` ASCII bytes as an [`", stringify!($ty), "`].")]
#[doc = ""]
#[doc = "Accepts standard textual float syntax (sign, exponent, `inf`,"]
#[doc = "`nan`). The entire `len`-byte field must be the number."]
pub fn [<$ty>](len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<$ty> {
move |input| {
take(len)
.and_then(terminated(float::<_, $ty, _>, eof))
.parse_next(input)
}
}
}
};
}
macro_rules! ascii_hex {
($ty:ty) => {
pastey::paste! {
#[cfg(feature = "ascii")]
#[inline(always)]
#[doc = concat!("Decodes exactly `len` ASCII hex bytes as a [`", stringify!($ty), "`].")]
#[doc = ""]
#[doc = "Accepts upper- or lower-case hex digits. The entire `len`-byte"]
#[doc = "field must be hexadecimal; any trailing byte is rejected."]
pub fn [<hex_ $ty>](len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<$ty> {
move |input| {
take(len)
.and_then(terminated(hex_uint::<_, $ty, _>, eof))
.parse_next(input)
}
}
}
};
}
macro_rules! ascii_run {
($name:ident, $parser:expr, $class:literal) => {
#[cfg(feature = "ascii")]
#[inline(always)]
#[doc = concat!("Decodes exactly `len` ASCII ", $class, " bytes into a [`String`].")]
#[doc = ""]
#[doc = concat!("Rejects the field unless every one of the `len` bytes is ", $class, ".")]
pub fn $name(len: usize) -> impl Fn(&mut &[u8]) -> crate::Result<String> {
move |input| {
take(len)
.and_then(terminated($parser, eof))
.map(|run: &[u8]| String::from_utf8_lossy(run).into_owned())
.parse_next(input)
}
}
};
}
ascii_uint!(u8);
ascii_uint!(u16);
ascii_uint!(u32);
ascii_uint!(u64);
ascii_uint!(u128);
ascii_int!(i8);
ascii_int!(i16);
ascii_int!(i32);
ascii_int!(i64);
ascii_int!(i128);
ascii_float!(f32);
ascii_float!(f64);
ascii_hex!(u8);
ascii_hex!(u16);
ascii_hex!(u32);
ascii_hex!(u64);
ascii_hex!(u128);
ascii_run!(alpha, alpha1, "alphabetic");
ascii_run!(digit, digit1, "decimal-digit");
ascii_run!(alphanumeric, alphanumeric1, "alphanumeric");