wasm4-impl 0.1.3

Procedural macros implementation for WASM-4 bindings
Documentation
use quote::ToTokens;
use syn::{parse::Parse, punctuated::Punctuated};

#[derive(Clone)]
pub struct Input {
    pub package: PackageField,
    pub input: InputField,
}

impl Parse for Input {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(Input {
            package: input.parse()?,
            input: input.parse()?,
        })
    }
}

#[derive(Clone)]
pub struct PackageField {
    pub _package_token: kw::package,
    pub _column: syn::Token![:],
    pub package_name: syn::Ident,
    pub _comma: syn::Token![,],
}

impl Parse for PackageField {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(PackageField {
            _package_token: input.parse()?,
            _column: input.parse()?,
            package_name: input.parse()?,
            _comma: input.parse()?,
        })
    }
}

#[derive(Clone)]
pub struct InputField {
    pub _input_token: kw::input,
    pub _column: syn::Token![:],
    pub _brace: syn::token::Brace,
    pub consts: Vec<ItemConst>,
    pub _comma: syn::Token![,],
}

impl Parse for InputField {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let content;
        Ok(InputField {
            _input_token: input.parse()?,
            _column: input.parse()?,
            _brace: syn::braced!(content in input),
            consts: {
                let mut consts = Vec::new();
                while !content.is_empty() {
                    consts.push(content.parse()?);
                }
                consts
            },
            _comma: input.parse()?,
        })
    }
}

#[derive(Clone)]
pub struct ItemConst<E = Expr> {
    pub attrs: Vec<syn::Attribute>,
    pub vis: syn::Visibility,
    pub const_token: syn::token::Const,
    pub ident: syn::Ident,
    pub colon_token: syn::token::Colon,
    pub ty: syn::token::Underscore,
    pub eq_token: syn::token::Eq,
    pub expr: E,
    pub semi_token: syn::token::Semi,
}

impl<E> ItemConst<E> {
    pub fn map_expr<F, U>(self, f: F) -> ItemConst<U>
    where
        F: FnOnce(E) -> U,
    {
        let ItemConst {
            attrs,
            vis,
            const_token,
            ident,
            colon_token,
            ty,
            eq_token,
            expr,
            semi_token,
        } = self;
        ItemConst {
            attrs,
            vis,
            const_token,
            ident,
            colon_token,
            ty,
            eq_token,
            expr: f(expr),
            semi_token,
        }
    }
}

impl<Expr: Parse> Parse for ItemConst<Expr> {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(ItemConst {
            attrs: input.call(syn::Attribute::parse_outer)?,
            vis: input.parse()?,
            const_token: input.parse()?,
            ident: input.parse()?,
            colon_token: input.parse()?,
            ty: input.parse()?,
            eq_token: input.parse()?,
            expr: input.parse()?,
            semi_token: input.parse()?,
        })
    }
}

#[derive(Clone)]
pub enum Expr {
    CommonPalette(CommonPaletteExpr),
    IncludeSprite(IncludeSpriteExpr),
}

impl Parse for Expr {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(kw::common_palette) {
            Ok(Expr::CommonPalette(input.parse()?))
        } else if lookahead.peek(kw::include_sprite) {
            Ok(Expr::IncludeSprite(input.parse()?))
        } else {
            Err(lookahead.error())
        }
    }
}

#[derive(Clone)]
pub struct CommonPaletteExpr {
    pub name_token: kw::common_palette,
    pub _bang_token: syn::token::Bang,
    pub _paren_token: syn::token::Paren,
    pub colors: Punctuated<Rgb, syn::token::Comma>,
}

impl Parse for CommonPaletteExpr {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let content;
        Ok(CommonPaletteExpr {
            name_token: input.parse()?,
            _bang_token: input.parse()?,
            _paren_token: syn::parenthesized!(content in input),
            colors: {
                let mut out = Punctuated::new();
                loop {
                    if content.is_empty() {
                        break;
                    }

                    if out.len() > 4 {
                        return Err(syn::Error::new(
                            content.span(),
                            "amount of specified palette colors should not be greater than 4",
                        ));
                    }

                    out.push_value(content.parse()?);
                    if let Some(comma) = content.parse()? {
                        out.push_punct(comma);
                    } else {
                        break;
                    }
                }
                content.parse::<syn::parse::Nothing>()?;
                out
            },
        })
    }
}

#[derive(Clone)]
pub struct IncludeSpriteExpr {
    pub name_token: kw::include_sprite,
    pub bang_token: syn::Token![!],
    pub paren_token: syn::token::Paren,
    pub path: syn::LitStr,
}

impl Parse for IncludeSpriteExpr {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let content;
        Ok(IncludeSpriteExpr {
            name_token: input.parse()?,
            bang_token: input.parse()?,
            paren_token: {
                let out = syn::parenthesized!(content in input);
                out
            },
            path: {
                let out = content.parse()?;
                content.parse::<syn::parse::Nothing>()?;
                out
            },
        })
    }
}

impl ToTokens for IncludeSpriteExpr {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.name_token.to_tokens(tokens);
        self.bang_token.to_tokens(tokens);
        self.paren_token
            .surround(tokens, |tokens| self.path.to_tokens(tokens));
    }
}

#[derive(Copy, Clone)]
pub struct Rgb {
    pub value: u32,
}

impl Parse for Rgb {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let lit: syn::LitInt = input.parse()?;
        let span = lit.span();
        if !matches!(lit.suffix(), "" | "u32") {
            return Err(syn::Error::new(
                span,
                "wrong suffix (should be u32 or nothing)",
            ));
        }
        let value = lit.base10_parse()?;
        Ok(Rgb { value })
    }
}

pub mod kw {
    syn::custom_keyword!(package);
    syn::custom_keyword!(input);
    syn::custom_keyword!(common_palette);
    syn::custom_keyword!(include_sprite);
    syn::custom_keyword!(bg);
}