macro_loop 0.4.3

A macro for writing repetitive Rust code using loops, conditionals, and bindings
Documentation
use derive_syn_parse::Parse;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{
    Error, Ident, LitStr, Token,
    ext::IdentExt,
    parse::{Parse, ParseStream, Parser},
    token::Bracket,
};

use super::*;

#[derive(Parse)]
pub struct FragConcat {
    #[bracket]
    _brackets: Bracket,
    #[inside(_brackets)]
    #[call(parse_segments)]
    segments: Vec<Segment>,
    #[inside(_brackets)]
    _type_arrow: Option<Token![=>]>,
    #[inside(_brackets)]
    #[parse_if(_type_arrow.is_some())]
    type_: Option<IdentStr>,
}

#[derive(Parse)]
enum Segment {
    #[peek_with(|input: ParseStream| input.peek(Ident::peek_any), name = "an ident")]
    Ident(#[call(Ident::parse_any)] Ident),

    #[peek(Token![@], name = "an ident")]
    Fragment(SegmentFragment),
}

#[derive(Parse)]
struct SegmentFragment {
    _at_token: Token![@],
    frag: Frag,
}

impl ApplyFragment for FragConcat {
    fn apply<'s: 'v, 'v>(
        &'s self,
        namespace: &mut Namespace<'v, 'v>,
        tokens: &mut TokenStream,
    ) -> syn::Result<()> {
        let str = self
            .segments
            .iter()
            .map(|seg| seg.try_to_string(namespace))
            .collect::<syn::Result<String>>()?;

        let span = self.segments.iter().map(|seg| seg.span()).nth(0).unwrap();

        let value = match self.type_.as_ref().map(|type_| type_.str()) {
            Some("str") => Value::Str(LitStr::new(&str, span)),
            None => Value::Ident(IdentStr::new(str.into_boxed_str(), span)),

            _ => return Err(Error::new_spanned(&self.type_, "invalid concat type")),
        };

        value.to_tokens(tokens);

        Ok(())
    }
}

impl Segment {
    fn try_to_string(&self, namespace: &Namespace) -> syn::Result<String> {
        Ok(match self {
            Segment::Ident(ident) => ident.to_string(),
            Segment::Fragment(frag) => {
                let mut namespace = namespace.fork();

                let mut frag_output = TokenStream::new();
                frag.frag.apply(&mut namespace, &mut frag_output)?;

                let expr = Expr::parse.parse2(frag_output)?;

                let value = Value::from_expr(&expr, &namespace)?;

                value.try_to_string()?
            }
        })
    }

    fn span(&self) -> Span {
        match self {
            Self::Ident(self_) => self_.span(),
            Segment::Fragment(self_) => self_._at_token.span,
        }
    }
}

fn parse_segments<T: Parse>(input: ParseStream) -> syn::Result<Vec<T>> {
    let mut items = Vec::new();

    items.push(input.parse()?);

    while !input.is_empty() && !input.peek(Token![=>]) {
        items.push(input.parse()?);
    }
    Ok(items)
}