macro_loop 0.4.3

A macro for writing repetitive Rust code using loops, conditionals, and bindings
Documentation
use std::mem::replace;

use derive_syn_parse::Parse;
use proc_macro2::{Delimiter, Group, Span};
use syn::{
    Error, Ident, Lit, LitBool, Token,
    parse::{Parse, Parser},
    parse2,
    punctuated::Punctuated,
    spanned::Spanned,
    token::Bracket,
};

use super::*;

pub enum Expr {
    Value(Value<'static>),
    Frag(ExprFrag),
    Bin(Box<ExprBin>),
    Un(Box<ExprUn>),
    Method(ExprMethod),
    List(ExprList),
    Paren(Box<Expr>),
}

pub struct ExprList {
    pub span: Span,
    pub items: Vec<Expr>,
}

pub struct ExprBin {
    pub lhs: Expr,
    pub op: BinOp,
    pub rhs: Expr,
}

pub struct ExprUn {
    pub op: UnOp,
    pub base: Expr,
}

#[derive(Parse)]
pub struct ExprFrag {
    pub _at_token: Token![@],
    pub frag: Box<Frag>,
}

pub struct ExprMethod {
    pub base: Box<Expr>,
    pub method: IdentStr,
    pub inputs: Vec<Expr>,
}

impl Parse for Expr {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let mut output = Expr::parse_single(input)?;

        while let Some(op) = BinOp::option_parse(input) {
            let rhs = Expr::parse_single(input)?;

            if let Expr::Bin(ref mut bin) = output {
                let ExprBin {
                    lhs: _,
                    op: output_op,
                    rhs: ref mut output_rhs,
                } = **bin;

                if output_op.lvl() > op.lvl() {
                    output_rhs.try_replace(|output_rhs| Self::bin(output_rhs, op, rhs))?;
                } else {
                    output.try_replace(|output| Self::bin(output, op, rhs))?;
                }
            } else {
                output.try_replace(|output| Self::bin(output, op, rhs))?;
            }
        }

        Ok(output)
    }
}

impl Expr {
    fn parse_single(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let mut output = Self::parse_base(input)?;

        loop {
            if input.peek(Token![.]) && !input.peek(Token![..]) {
                input.parse::<Token![.]>().unwrap();

                let method = input.parse::<IdentStr>()?;

                let inputs = input.parse::<Group>()?;
                if inputs.delimiter() != Delimiter::Parenthesis {
                    return Err(Error::new(inputs.span(), "expected `()`"));
                }

                let inputs = Punctuated::<Expr, Token![,]>::parse_terminated
                    .parse2(inputs.stream())?
                    .into_iter()
                    .collect();

                output.replace(|output| {
                    Self::Method(ExprMethod {
                        base: Box::new(output),
                        method,
                        inputs,
                    })
                });

                continue;
            }

            if input.peek(Bracket) {
                let group = input.parse::<Group>()?;

                let idx = Expr::parse.parse2(group.stream())?;

                output.replace(|output| {
                    Self::Method(ExprMethod {
                        base: Box::new(output),
                        method: IdentStr::new("index", group.span()),
                        inputs: vec![idx],
                    })
                });
            }

            break;
        }

        Ok(output)
    }

    fn parse_base(input: syn::parse::ParseStream) -> syn::Result<Self> {
        if let Some(op) = UnOp::option_parse(input) {
            let base = Expr::parse_single(input)?;

            return Self::un(op, base);
        };

        if let Some(lit) = input.parse::<Option<Lit>>()? {
            return Ok(Self::Value(Value::from_lit(lit)?));
        };

        if input.peek(Ident) {
            return Ok(Self::Value(Value::Ident(input.parse().unwrap())));
        };

        if let Some(group) = input.parse::<Option<Group>>()? {
            return Ok(match group.delimiter() {
                Delimiter::None => {
                    return Err(Error::new(group.span(), "unsupported delimiters"));
                }

                Delimiter::Brace => {
                    return Err(Error::new(
                        group.delim_span().span(),
                        "blocks are currently unsupported",
                    ));
                }

                Delimiter::Bracket => {
                    let punctuated =
                        Punctuated::<_, Token![,]>::parse_terminated.parse2(group.stream())?;

                    Self::List(ExprList {
                        span: group.span(),
                        items: punctuated.into_iter().collect(),
                    })
                }

                Delimiter::Parenthesis => Self::Paren(Box::new(parse2(group.stream())?)),
            });
        };

        if let Some(at_token) = input.parse::<Option<Token![@]>>()? {
            let frag = input.parse()?;

            return Ok(Self::Frag(ExprFrag {
                _at_token: at_token,
                frag,
            }));
        };

        Err(input.error("expected an expression"))
    }

    fn bin(self, op: BinOp, rhs: Self) -> syn::Result<Self> {
        Ok(
            if let (Self::Value(self_), Self::Value(rhs)) = (&self, &rhs) {
                Self::Value(self_.bin_op(op, rhs)?)
            } else {
                Self::Bin(Box::new(ExprBin { lhs: self, op, rhs }))
            },
        )
    }

    fn un(op: UnOp, base: Self) -> syn::Result<Self> {
        Ok(if let Self::Value(base) = base {
            Self::Value(base.un_op(op)?)
        } else {
            Self::Un(Box::new(ExprUn { op, base }))
        })
    }

    fn replace(&mut self, value: impl FnOnce(Self) -> Self) {
        *self = value(replace(self, Self::temporary()));
    }

    fn try_replace(&mut self, value: impl FnOnce(Self) -> syn::Result<Self>) -> syn::Result<()> {
        *self = value(replace(self, Self::temporary()))?;

        Ok(())
    }

    fn temporary() -> Self {
        Self::Value(Value::Bool(LitBool::new(false, Span::call_site())))
    }
}