cuicui_chirp 0.12.0

A file format based on cuicui_dsl to describe bevy UIs
Documentation
use winnow::combinator::{opt, preceded};
use winnow::error::ErrMode::{Backtrack, Cut};
use winnow::token::any;
use winnow::Parser;

use super::generic::{Delimited, Many, SepList, Terminated};
use super::tokens::{ident, many_tts};
use super::{AddNodes, BlockResult};
use crate::parser::ast::{self, Ast, AstBuilder, ChirpFileHeader, MethodHeader, WriteHeader};
use crate::parser::ast::{ArgumentHeader, IdentOffset, ImportHeader};
use crate::parser::ast::{CodeHeader, FnHeader, SpawnHeader, StKind, TemplateHeader};
use crate::parser::stream::{tokens as t, Input, Token};
use crate::parser::Error;

type Opt<T> = Option<T>;
type Sep<T> = SepList<T>;
type Paren<T> = Delimited<T, t::Lparen, t::Rparen>;
type Curly<T> = Delimited<T, t::Lcurly, t::Rcurly>;

#[rustfmt::skip]
macro_rules! Tokens {
    ($inner:ty, ')') => { Terminated<$inner, t::Rparen> };
    ($inner:ty, ']') => { Terminated<$inner, t::Rbracket> };
    ($inner:ty, '}') => { Terminated<$inner, t::Rcurly> };
}
#[rustfmt::skip]
macro_rules! tokens {
    ('(' $inner:expr, ')') => { winnow::combinator::delimited(t::Lparen, $inner, t::Rparen) };
}
#[rustfmt::skip]
macro_rules! token {
    ($first:tt $(| $many:tt)*) => { token!(@ $first) $(| token!(@ $many))* };
    (@ "ident")  => { Token::Ident(_) };
    (@ "string") => { Token::String(_) };
    (@ '(') => { Token::Lparen };
    (@ ')') => { Token::Rparen };
    (@ '{') => { Token::Lcurly };
    (@ '}') => { Token::Rcurly };
    (@ '[') => { Token::Lbracket };
    (@ ']') => { Token::Rbracket };
    (@ ',') => { Token::Comma };
    (@ '=') => { Token::Equal };
}

struct Import;
impl AddNodes for Import {
    fn add_node(input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
        let import = |input: &mut Input| {
            preceded(t::Use, (ident, opt(preceded(t::As, ident)))).parse_next(input)
        };
        let (name, alias) = import(input)?;
        builder.write_header(ImportHeader { name, alias: alias.into() });
        Ok(ImportHeader::SIZE)
    }
}

struct Argument;
impl AddNodes for Argument {
    fn add_node(input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
        let (start, end) = many_tts::<true>(input)?;
        builder.write_header(ArgumentHeader { start, end });
        Ok(ArgumentHeader::SIZE)
    }
}
impl AddNodes for ast::IdentOffset {
    fn add_node(input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
        let ident = ident(input)?;
        builder.write_header(ident);
        Ok(Self::SIZE)
    }
}

struct Fn;
impl AddNodes for Fn {
    fn add_node(input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
        t::Fn.parse_next(input)?;
        let header = builder.reserve_header();

        let name = ident(input)?;
        let parameter_len = Paren::<Sep<IdentOffset>>::add_node(input, builder)?;
        let body_len = Curly::<Sep<St>>::add_node(input, builder)?;

        let parameter_count = parameter_len / IdentOffset::SIZE;
        builder.write(header, FnHeader { parameter_count, name });
        Ok(FnHeader::SIZE + parameter_len + body_len)
    }
}

struct St;
impl AddNodes for St {
    fn add_node(input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
        use Token::{Ident, String as TStr};

        let start = input.next_start();
        match any.parse_next(input)? {
            TStr(name) | Ident(name) if name.ends_with(b"!") => {
                add_template(start.into(), input, builder)
            }
            TStr(name) | Ident(name) if name == b"code" => {
                let name = tokens!('(' ident, ')').parse_next(input)?;
                builder.write_header((StKind::Code, CodeHeader { name }));
                Ok(CodeHeader::SIZE)
            }
            TStr(name) | Ident(name) => {
                let not_empty = ![b"Entity", &b"spawn"[..]].contains(&name);
                add_spawn(not_empty.then_some(start), input, builder)
            }
            token!('}') => Err(Backtrack(Error::Unexpected)),
            bad_token => Err(Cut(Error::StartStatement(Some(bad_token).into()))),
        }
    }
}
fn add_template(name: IdentOffset, input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
    let header = builder.reserve_header();

    let argument_len = Paren::<Sep<Argument>>::add_node(input, builder)?;
    let methods_len = Opt::<Paren<Many<Method>>>::add_node(input, builder)?;
    let children_len = Opt::<Curly<Many<St>>>::add_node(input, builder)?;

    let argument_count = argument_len / ArgumentHeader::SIZE;
    let writer = (
        StKind::Template,
        TemplateHeader { name, argument_count, methods_len, children_len },
    );
    builder.write(header, writer);
    Ok(TemplateHeader::SIZE + argument_len + methods_len + children_len)
}

fn add_spawn(name: Option<u32>, input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
    let header = builder.reserve_header();

    let (methods_len, children_len) = match any.parse_next(input)? {
        token!('(') => {
            let methods = <Tokens![Many<Method>, ')']>::add_node(input, builder)?;
            let children = Opt::<Curly<Many<St>>>::add_node(input, builder)?;
            (methods, children)
        }
        token!('{') => (0, <Tokens![Many<St>, '}']>::add_node(input, builder)?),
        bad_token => return Err(Cut(Error::StatementDelimiter(Some(bad_token).into()))),
    };
    let name = name.into();
    let writer = (
        StKind::Spawn,
        SpawnHeader { name, methods_len, children_len },
    );
    builder.write(header, writer);
    Ok(SpawnHeader::SIZE + methods_len + children_len)
}

struct Method;
impl AddNodes for Method {
    fn add_node(input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
        // Note: header created after name so that we don't create it when closing method zone
        let name = ident.parse_next(input)?;
        let header = builder.reserve_header();

        let argument_len = Opt::<Paren<SepList<Argument>>>::add_node(input, builder)?;

        let argument_count = argument_len / ArgumentHeader::SIZE;
        builder.write(header, MethodHeader { argument_count, name });
        Ok(MethodHeader::SIZE + argument_len)
    }
}

struct ChirpFile;
impl AddNodes for ChirpFile {
    fn add_node(input: &mut Input, builder: &mut AstBuilder) -> BlockResult {
        let header = builder.reserve_header();

        let import_len = Many::<Import>::add_node(input, builder)?;
        let fn_len = Many::<Fn>::add_node(input, builder)?;
        let root_statement_len = St::add_node(input, builder)?;

        let import_count = import_len / ImportHeader::SIZE;
        let root_statement_offset = ChirpFileHeader::SIZE + import_len + fn_len;
        builder.write(
            header,
            ChirpFileHeader { import_count, root_statement_offset },
        );
        Ok(root_statement_offset + root_statement_len)
    }
}

pub fn chirp_file(mut input: Input) -> Result<Ast, (Error, (u32, u32))> {
    let mut ast_builder = AstBuilder::new();
    let result = ChirpFile::add_node(&mut input, &mut ast_builder);
    let offset = input.current_offset();

    match result {
        Ok(_) if input.is_empty() => Ok(ast_builder.build()),
        Ok(_) => Err((Error::TrailingText, (offset, offset))),
        Err(Cut(err) | Backtrack(err)) => Err((err, (offset, offset))),
        _ => unreachable!(),
    }
}