use bevy::math::{Vec2, Vec3};
use bevy::reflect::Reflect;
use winnow::Parser;
use winnow::ascii::{multispace0, take_escaped};
use winnow::combinator::*;
use winnow::error::{ErrMode, StrContext};
use winnow::seq;
use winnow::token::{none_of, one_of};
use crate::ParserContext;
use crate::context::Error;
pub trait ArgParser: Sized {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self>;
}
pub fn trim<'a, ParseNext, Output>(mut parser: ParseNext) -> impl Parser<&'a str, Output, Error>
where
ParseNext: Parser<&'a str, Output, Error>,
{
move |input: &mut &'a str| {
multispace0(input)?;
let result = parser.parse_next(input)?;
multispace0(input)?;
Ok(result)
}
}
pub fn tuple_struct<'a, Inner, Out>(
ident: &'static str,
mut inner: Inner,
) -> impl Parser<&'a str, Out, Error>
where
Inner: Parser<&'a str, Out, Error>,
{
move |input: &mut &'a str| {
(
ident.label("struct ident").expected_str(ident),
cut_err('(').label("opening delimiter").expected_char('('),
)
.parse_next(input)?;
let inner = inner.parse_next(input).map_err(ErrMode::cut)?;
cut_err(')')
.label("closing delimiter")
.expected_char(')')
.parse_next(input)?;
Ok(inner)
}
}
impl<T: ArgParser> ArgParser for std::ops::Range<T> {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
range(input)
}
}
pub fn range<T: ArgParser>(input: &mut &str) -> winnow::ModalResult<std::ops::Range<T>> {
seq! {
std::ops::Range {
start: T::parse_arg,
_: "..",
end: T::parse_arg
}
}
.parse_next(input)
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
pub struct Milliseconds(pub f32);
impl ArgParser for Milliseconds {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
duration_millis(input).map(Self)
}
}
pub fn duration_millis(input: &mut &str) -> winnow::ModalResult<f32> {
Ok(duration(input)?.into_millis())
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
pub struct Seconds(pub f32);
impl ArgParser for Seconds {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
duration_secs(input).map(Self)
}
}
pub fn duration_secs(input: &mut &str) -> winnow::ModalResult<f32> {
Ok(duration(input)?.into_secs())
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
pub struct Minutes(pub f32);
impl ArgParser for Minutes {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
duration_mins(input).map(Self)
}
}
pub fn duration_mins(input: &mut &str) -> winnow::ModalResult<f32> {
Ok(duration(input)?.into_mins())
}
struct Duration {
value: f32,
unit: Option<Unit>,
}
impl Duration {
pub fn into_millis(self) -> f32 {
match self.unit {
Some(unit) => match unit {
Unit::Milliseconds => self.value,
Unit::Seconds => self.value * 1_000f32,
Unit::Minutes => self.value * 1_000f32 * 60f32,
},
None => self.value,
}
}
pub fn into_secs(self) -> f32 {
match self.unit {
Some(unit) => match unit {
Unit::Milliseconds => self.value / 1_000f32,
Unit::Seconds => self.value,
Unit::Minutes => self.value * 60f32,
},
None => self.value,
}
}
pub fn into_mins(self) -> f32 {
match self.unit {
Some(unit) => match unit {
Unit::Milliseconds => self.value / 1_000f32 / 60f32,
Unit::Seconds => self.value / 60f32,
Unit::Minutes => self.value,
},
None => self.value,
}
}
}
fn duration(input: &mut &str) -> winnow::ModalResult<Duration> {
seq! {
Duration {
value: f32::parse_arg,
unit: opt(unit)
}
}
.parse_next(input)
}
enum Unit {
Milliseconds,
Seconds,
Minutes,
}
fn unit(input: &mut &str) -> winnow::ModalResult<Unit> {
alt((
"ms".map(|_| Unit::Milliseconds),
"s".map(|_| Unit::Seconds),
"m".map(|_| Unit::Minutes),
))
.parse_next(input)
}
impl ArgParser for f32 {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
if let Some(range_pos) = input.find("..") {
let prefix = &input[..range_pos];
if !prefix.is_empty() && prefix.chars().all(|c| c == '-' || c.is_ascii_digit()) {
return winnow::ascii::dec_int::<_, i32, _>
.map(|v| v as f32)
.label("float")
.parse_next(input);
}
}
winnow::ascii::float.label("float").parse_next(input)
}
}
impl ArgParser for f64 {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
if let Some(range_pos) = input.find("..") {
let prefix = &input[..range_pos];
if !prefix.is_empty() && prefix.chars().all(|c| c == '-' || c.is_ascii_digit()) {
return winnow::ascii::dec_int::<_, i64, _>
.label("float")
.map(|v| v as f64)
.parse_next(input);
}
}
winnow::ascii::float.label("float").parse_next(input)
}
}
macro_rules! primitive_parser {
($ty:ty, $parser:ident) => {
impl ArgParser for $ty {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
winnow::ascii::$parser
.context(StrContext::Label(stringify!($ty)))
.parse_next(input)
}
}
};
($ty:ty, $parser:ident => $label:expr) => {
impl ArgParser for $ty {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
winnow::ascii::$parser
.context(StrContext::Label($label))
.parse_next(input)
}
}
};
}
primitive_parser!(u8, dec_uint);
primitive_parser!(u16, dec_uint);
primitive_parser!(u32, dec_uint);
primitive_parser!(u64, dec_uint);
primitive_parser!(usize, dec_uint);
primitive_parser!(i8, dec_int);
primitive_parser!(i16, dec_int);
primitive_parser!(i32, dec_int);
primitive_parser!(i64, dec_int);
primitive_parser!(isize, dec_int);
impl ArgParser for bool {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
alt(("true".map(|_| true), "false".map(|_| false)))
.label("bool")
.expected_str("true")
.expected_str("false")
.parse_next(input)
}
}
impl ArgParser for String {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
delimited(
'"',
take_escaped(
none_of(['\\', '"']),
'\\',
one_of(['"', '\\', 'n', 't', 'r']),
),
'"',
)
.map(str::to_string)
.label("string")
.parse_next(input)
}
}
impl<T: ArgParser> ArgParser for Option<T> {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
alt((
tuple_struct("some", T::parse_arg.map(Option::Some)),
"none".map(|_| None),
fail.label("option")
.expected_str("none")
.expected_str("some( /* value */ )"),
))
.parse_next(input)
}
}
impl ArgParser for Vec2 {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
cut_err(tuple_struct(
"vec2",
seq!(
trim(winnow::ascii::float),
_: ',',
trim(winnow::ascii::float),
),
))
.map(|(x, y)| Vec2::new(x, y))
.parse_next(input)
}
}
impl ArgParser for Vec3 {
fn parse_arg(input: &mut &str) -> winnow::ModalResult<Self> {
cut_err(tuple_struct(
"vec3",
seq!(
trim(winnow::ascii::float),
_: ',',
trim(winnow::ascii::float),
_: ',',
trim(winnow::ascii::float),
),
))
.map(|(x, y, z)| Vec3::new(x, y, z))
.parse_next(input)
}
}
#[cfg(test)]
mod test {
use super::*;
#[track_caller]
fn parse<T: ArgParser + PartialEq + std::fmt::Debug>(v: T, mut str: &str) {
let vp = T::parse_arg(&mut str).unwrap();
assert_eq!(v, vp);
}
#[test]
fn simple() {
parse(3.4, "3.4");
parse(44u32, "44");
parse(Vec2::new(1.9, 2.3), "vec2(1.9, 2.3)");
parse(7u32..9, "7..9");
parse(4.37..9.73, "4.37..9.73");
parse(4.1..9.0, "4.1..9");
parse(Some(3), "some(3)");
parse::<Option<u32>>(None, "none");
parse(true, "true");
parse(false, "false");
parse(String::from("Hello, World!"), "\"Hello, World!\"");
parse(String::from("Hello, \nWorld!"), "\"Hello, \nWorld!\"");
}
}