bevy_state_plugin_generator 1.4.4

A build-dependency that generates a Bevy State Plugin from a simple state definition.
Documentation
use nom::branch::alt;
use nom::bytes::complete::*;
use nom::character::complete::*;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::sequence::*;
use nom::{IResult, Parser};

use crate::parsing::{Comment, Identifier, Node, Token};

pub fn parse_comment(input: &str) -> IResult<&str, Node<'_>> {
    comment(input).map_result(Node::Comment)
}

pub fn comment(input: &str) -> IResult<&str, Comment<'_>> {
    use nom::combinator::eof;

    delimited(skip(tag("//")), not_line_ending, alt((eof, line_ending)))
        .parse(input)
        .map_result(|c| c.trim().into())
}

pub fn identifier(input: &str) -> IResult<&str, Identifier<'_>> {
    recognize(pair(
        take_till1(|c: char| !c.is_uppercase()),
        take_while(|c: char| c.is_alphanumeric() || c == '_'),
    ))
    .parse(input)
    .map_result(Identifier::from)
}

fn skip<P, T, O, E>(parser: P) -> impl Parser<T, Output = O, Error = E>
where
    T: nom::Input,
    P: Parser<T, Output = O, Error = E>,
    <T as nom::Input>::Item: nom::AsChar,
    E: nom::error::ParseError<T>,
{
    preceded(multispace0, parser)
}

pub fn separator(input: &str) -> IResult<&str, Token> {
    skip(tag(",")).parse(input).map_result(|_| Token::Separator)
}

pub fn open_enum(input: &str) -> IResult<&str, Token> {
    skip(tag("{")).parse(input).map_result(|_| Token::OpenEnum)
}

pub fn close_enum(input: &str) -> IResult<&str, Token> {
    skip(tag("}")).parse(input).map_result(|_| Token::CloseEnum)
}

pub fn open_list(input: &str) -> IResult<&str, Token> {
    skip(tag("[")).parse(input).map_result(|_| Token::OpenList)
}

pub fn close_list(input: &str) -> IResult<&str, Token> {
    skip(tag("]")).parse(input).map_result(|_| Token::CloseList)
}

pub trait MapResult<'a, I, O1, O2> {
    fn map_result(self, callback: impl Fn(O1) -> O2) -> IResult<I, O2>;
}

impl<I, O1, O2> MapResult<'_, I, O1, O2> for IResult<I, O1> {
    fn map_result(self, callback: impl Fn(O1) -> O2) -> IResult<I, O2> {
        self.map(|(rest, result)| (rest, callback(result)))
    }
}

pub fn parse_config(input: &str) -> IResult<&str, Vec<Node<'_>>> {
    many0(delimited(many0(separator), parse_node, many0(separator))).parse(input)
}

pub fn parse_node(input: &'_ str) -> IResult<&'_ str, Node<'_>> {
    alt((parse_enum, parse_list, parse_comment, parse_singleton)).parse(input)
}

pub fn parse_singleton(input: &'_ str) -> IResult<&'_ str, Node<'_>> {
    skip(identifier).parse(input).map_result(Node::singleton)
}

pub fn parse_enum(input: &str) -> IResult<&str, Node<'_>> {
    let (input, name) = terminated(skip(identifier), skip(open_enum)).parse(input)?;
    let (input, children) = skip(parse_elements_until(close_enum)).parse(input)?;
    Ok((input, Node::Enum(name, children)))
}

pub fn parse_list(input: &'_ str) -> IResult<&'_ str, Node<'_>> {
    let (input, name) = skip(identifier).parse(input)?;
    let (input, children) =
        skip(preceded(open_list, parse_elements_until(close_list))).parse(input)?;
    Ok((input, Node::List(name, children)))
}

pub fn parse_elements_until<'a>(
    until: impl Fn(&'a str) -> IResult<&'a str, Token> + Copy,
) -> impl Fn(&'a str) -> IResult<&'a str, Vec<Node<'a>>> {
    move |input: &'a str| {
        delimited(
            // ignore any leading whitespace and commas
            many0(separator),
            // 0 or more elements, ignoring whitespace and commas
            many0(alt((terminated(parse_node, many0(separator)),))),
            // then expect the closing token
            until,
        )
        .parse(input)
    }
}