use winnow::{
Parser,
ascii::{dec_int, dec_uint},
combinator::{alt, delimited, eof, preceded, repeat, separated, trace},
error::ContextError,
stream::{AsChar, Stream, StreamIsPartial},
token::{literal, rest, take, take_till, take_until, take_while},
};
pub trait WinnowParser<I, T> = Parser<I, T, ContextError>;
pub fn le_f16(input: &mut &[u8]) -> winnow::Result<f16> {
let parser = take_array().map(|bytes: [u8; 2]| f16::from_le_bytes(bytes));
winnow::trace!("le_f16", parser).parse_next(input)
}
pub fn parse_bool(input: &mut &str) -> winnow::Result<bool> {
let parser = |input: &mut &str| {
let uint: u32 = dec_uint.parse_next(input)?;
match uint {
0 => Ok(false),
1 => Ok(true),
_ => Err(ContextError::new()),
}
};
winnow::trace!("parse_bool", parser).parse_next(input)
}
pub fn safe_u32(input: &mut &str) -> winnow::Result<u32> {
let parser = |input: &mut &str| {
let uint = dec_uint.parse_next(input)?;
if input.starts_with('.') {
Err(ContextError::new())
} else {
Ok(uint)
}
};
winnow::trace!("safe_u32", parser).parse_next(input)
}
pub fn uint(input: &mut &str) -> winnow::Result<u32> {
winnow::trace!(
"uint",
take_while(1.., AsChar::is_dec_digit).try_map(|x: &str| x.parse::<u32>())
)
.parse_next(input)
}
pub fn nullable_uint<'a>() -> impl WinnowParser<&'a str, Option<u32>> {
winnow::trace!(
"nullable_uint",
dec_int.map(|i: i32| match i {
-1 => None,
0.. => Some(i as u32),
_ => unreachable!("-1 or 0+ expected"),
})
)
}
pub fn space_or_nl1<'a>(input: &mut &'a str) -> winnow::Result<&'a str> {
winnow::trace!(
"space_or_nl1",
take_while(1.., |c: char| {
c == ' ' || c == '\t' || c == '\r' || c == '\n'
})
)
.parse_next(input)
}
pub fn space_or_nl0<'a>(input: &mut &'a str) -> winnow::Result<&'a str> {
winnow::trace!(
"space_or_nl0",
take_while(0.., |c: char| {
c == ' ' || c == '\t' || c == '\r' || c == '\n'
})
)
.parse_next(input)
}
pub fn comment_multiline<'a>() -> impl WinnowParser<&'a str, &'a str> {
winnow::trace!(
"multiline_comment",
delimited("/*", take_until(0.., "*/"), "*/")
)
}
pub fn comment_single_line<'a>() -> impl WinnowParser<&'a str, &'a str> {
winnow::trace!(
"single_line_comment",
preceded("//", take_while(0.., |c| !(c == '\r' || c == '\n'))) )
}
pub fn spaces_or_comments<'a>() -> impl WinnowParser<&'a str, String> {
let part_parser = alt((space_or_nl1, comment_multiline(), comment_single_line()));
winnow::trace!(
"spaces_or_comments",
repeat(1.., part_parser).map(|parts: Vec<&str>| parts.concat())
)
}
pub fn quoted<'a>(quote: char) -> impl WinnowParser<&'a str, &'a str> {
let name = format!("{}({:?})", winnow::trace_name!("quoted"), quote);
trace(
name,
delimited(
quote, take_until(0.., quote),
quote,
),
)
}
pub fn unquoted<'a>() -> impl WinnowParser<&'a str, &'a str> {
winnow::trace!("unquoted", take_till(1.., char::is_whitespace))
}
pub fn quoted_str(input: &mut &str) -> winnow::Result<String> {
winnow::trace!("quoted_str", quoted('"').map(String::from)).parse_next(input)
}
pub fn single_quoted_str(input: &mut &str) -> winnow::Result<String> {
winnow::trace!("single_quoted_str", quoted('\'').map(String::from)).parse_next(input)
}
pub fn unquoted_str(input: &mut &str) -> winnow::Result<String> {
winnow::trace!("unquoted_str", unquoted().map(String::from)).parse_next(input)
}
pub fn filename<'a>(extension: &str) -> impl WinnowParser<&'a str, String> {
let ext = format!(".{extension}");
let name = format!("{}({:?})", winnow::trace_name!("filename"), extension);
trace(
name,
rest.verify(move |s: &str| s.ends_with(&ext))
.map(String::from),
)
}
pub fn optional_filename<'a>(extension: &str) -> impl WinnowParser<&'a str, Option<String>> {
let name = format!(
"{}({:?})",
winnow::trace_name!("optional_filename"),
extension
);
trace(
name,
alt((
eof.map(|_| None), filename(extension).map(Some),
)),
)
}
pub fn version_line<'a>() -> impl WinnowParser<&'a str, u32> {
winnow::trace!("version_line", preceded(literal("version "), dec_uint))
}
pub fn quoted_comma_separated<'a>() -> impl WinnowParser<&'a str, Vec<String>> {
winnow::trace!(
"quoted_comma_separated",
quoted('"').and_then(separated(
1..,
take_while(1.., |c| c != ',').map(String::from),
literal(", "),
))
)
}
pub fn separated_array<const N: usize, I, PO, S, P, SO>(
sep: S,
item: P,
) -> impl WinnowParser<I, [PO; N]>
where
I: Stream,
P: WinnowParser<I, PO>,
S: WinnowParser<I, SO>,
{
winnow::trace!(
"separated_array",
separated(N, item, sep).map(|x: Vec<_>| {
x.try_into()
.unwrap_or_else(|_| unreachable!("Parser should take care of length"))
})
)
}
pub fn repeat_array<const N: usize, I, O>(
parser: impl WinnowParser<I, O>,
) -> impl WinnowParser<I, [O; N]>
where
I: Stream,
{
winnow::trace!(
"repeat_array",
repeat(N, parser).map(|x: Vec<_>| {
x.try_into()
.unwrap_or_else(|_| unreachable!("Parser should take care of length"))
})
)
}
pub fn take_array<const N: usize, I>() -> impl WinnowParser<I, [I::Token; N]>
where
I: Stream + StreamIsPartial,
I::Slice: TryInto<[I::Token; N]>,
{
winnow::trace!(
"repeat_array",
take(N).map(|x: I::Slice| x
.try_into()
.unwrap_or_else(|_| unreachable!("Parser should take care of length")))
)
}
#[cfg(test)]
mod tests {
use winnow::Parser;
use super::unquoted;
#[test]
fn test_unquoted() {
let input = "hello";
let x = unquoted().parse(input).unwrap();
assert_eq!(x, "hello");
}
}