workers-rsx-impl 0.1.0

Proc macros for workers-rsx
Documentation
use crate::child::Child;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result};

#[derive(Default)]
pub struct Children {
    pub nodes: Vec<Child>,
}

impl Children {
    pub fn new(nodes: Vec<Child>) -> Self {
        Children { nodes }
    }

    pub fn len(&self) -> usize {
        self.nodes.len()
    }

    fn has_let_statements(&self) -> bool {
        self.nodes.iter().any(|c| matches!(c, Child::LetStatement(_)))
    }

    /// Parse children until the stream is empty (for brace-delimited blocks)
    pub fn parse_until_empty(input: ParseStream) -> Result<Self> {
        let mut nodes = vec![];
        while !input.is_empty() {
            let child = input.parse::<Child>()?;
            nodes.push(child);
        }
        Ok(Self::new(nodes))
    }

    /// Render children as imperative code writing into a buffer.
    /// Used when let statements are present and children need shared scope.
    fn as_buffered_render_tokens(&self) -> proc_macro2::TokenStream {
        let buf_ident = syn::Ident::new("__rsx_children_buf", proc_macro2::Span::call_site());

        let render_stmts: Vec<_> = self
            .nodes
            .iter()
            .map(|child| {
                match child {
                    Child::LetStatement(stmt) => {
                        // Emit the let statement directly into the block scope
                        quote! { #stmt }
                    }
                    _ => {
                        // Render any other child into the buffer
                        quote! {
                            workers_rsx::Render::render_into(#child, &mut #buf_ident).unwrap();
                        }
                    }
                }
            })
            .collect();

        quote! {{
            let mut #buf_ident = String::new();
            #(#render_stmts)*
            workers_rsx::RawOwned(#buf_ident)
        }}
    }

    /// Return children as a tuple token stream (without Option wrapping)
    pub fn as_tuples_tokens(&self) -> proc_macro2::TokenStream {
        if self.has_let_statements() {
            return self.as_buffered_render_tokens();
        }

        let children_quotes: Vec<_> = self
            .nodes
            .iter()
            .map(|child| {
                quote! { #child }
            })
            .collect();

        match children_quotes.len() {
            0 => quote! { () },
            1 => quote! { #(#children_quotes)* },
            _ => {
                let mut iter = children_quotes.iter();

                let first = iter.next().unwrap();
                let second = iter.next().unwrap();

                iter.fold(
                    quote!((#first, #second)),
                    |renderable, current| quote!((#renderable, #current)),
                )
            }
        }
    }

    pub fn as_option_of_tuples_tokens(&self) -> proc_macro2::TokenStream {
        if self.has_let_statements() {
            let buffered = self.as_buffered_render_tokens();
            return quote! { Some(#buffered) };
        }

        let children_quotes: Vec<_> = self
            .nodes
            .iter()
            .map(|child| {
                quote! { #child }
            })
            .collect();

        match children_quotes.len() {
            0 => quote! { Option::<()>::None },
            1 => quote! { Some(#(#children_quotes),*) },
            _ => {
                let mut iter = children_quotes.iter();

                let first = iter.next().unwrap();
                let second = iter.next().unwrap();

                let tuple_of_tuples = iter.fold(
                    quote!((#first, #second)),
                    |renderable, current| quote!((#renderable, #current)),
                );

                quote! { Some(#tuple_of_tuples) }
            }
        }
    }
}

impl Parse for Children {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut nodes = vec![];

        while !input.peek(syn::Token![<]) || !input.peek2(syn::Token![/]) {
            let child = input.parse::<Child>()?;
            nodes.push(child);
        }

        Ok(Self::new(nodes))
    }
}

impl ToTokens for Children {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.as_option_of_tuples_tokens().to_tokens(tokens);
    }
}