vertigo-macro 0.2.0-alpha

Reactive Real-DOM library for Rust - macros
Documentation
use std::collections::VecDeque;

use proc_macro::{TokenStream, TokenTree, Span};
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::Ident as Ident2;
use syn::__private::quote::format_ident;

fn is_char(token: &TokenTree, char: char) -> bool {
    if let TokenTree::Punct(inner) = token {
        inner.as_char() == char
    } else {
        false
    }
}

fn find_param_name(params: &[TokenTree]) -> Option<Ident2> {
    if params.len() == 1 {
        if let Some(first) = params.first() {
            return if let TokenTree::Ident(value) = &first {
                Some(format_ident!("{}", value.to_string()))
            } else {
                emit_error!(Span::call_site(), "Can't find variable name, expected ident (1)");
                None
            };
        }
    }

    emit_error!(Span::call_site(), "Can't find variable name, expected ident (2)");
    None
}

fn is_first_pipe_char(list: &[TokenTree]) -> bool {
    let Some(first) = list.first() else {
        return false;
    };

    let TokenTree::Punct(char) = first else {
        return false;
    };

    char.as_char() == '|'
}

struct TokensParamsBody<'a> {
    bind_params: Vec<&'a [TokenTree]>,
    func_params: Option<Vec<&'a [TokenTree]>>,
    body: Vec<TokenTree>,
}

fn is_bracket_contain(tokens: &[TokenTree]) -> bool {
    for token in tokens {
        if let TokenTree::Punct(inner) = token {
            if inner.as_char() == '|' {
                return true;
            }
        }
    }

    false
}

fn split_params_and_body_function(tokens: &[TokenTree]) -> Result<TokensParamsBody, String> {
    let mut chunks = tokens
        .split(|token| {
            is_char(token, '|')
        })
        .collect::<VecDeque<_>>();

    if chunks.len() != 3 {
        return Err("Two brackets '|' were expected".to_string());
    }

    let bind_params = chunks
        .pop_front()
        .unwrap()
        .split(|token| {
            is_char(token, ',')
        })
        .filter(|item| !item.is_empty())
        .collect::<Vec<_>>();

    let func_params = chunks
        .pop_front()
        .unwrap()
        .split(|token| {
            is_char(token, ',')
        })
        .filter(|item| !item.is_empty())
        .collect::<Vec<_>>();
    
    let body = {
        let mut occurred_bracket = false;
        let mut body = Vec::new();
        for token in tokens {
            if is_char(token, '|') {
                occurred_bracket = true;
            }

            if occurred_bracket {
                body.push(token.clone());
            }
        }

        body
    };

    Ok(TokensParamsBody {
        bind_params,
        func_params: Some(func_params),
        body,
    })
}

fn split_params_and_body_block(tokens: &[TokenTree]) -> Result<TokensParamsBody, String> {
    let mut chunks = tokens
        .split(|token| {
            is_char(token, ',')
        })
        .collect::<Vec<_>>();

    let body = chunks.pop().unwrap().to_vec();

    Ok(TokensParamsBody {
        bind_params: chunks,
        func_params: None,
        body,
    })
}

fn split_params_and_body(tokens: &[TokenTree]) -> Result<TokensParamsBody, String> {
    let bracket_contain = is_bracket_contain(tokens);

    if bracket_contain {
        split_params_and_body_function(tokens)
    } else {
        split_params_and_body_block(tokens)
    }
}

pub fn bind_macro_fn(input: TokenStream) -> Result<TokenStream, String> {
    let tokens = input.into_iter().collect::<Vec<_>>();

    let TokensParamsBody { bind_params, func_params: _, body } = split_params_and_body(tokens.as_slice())?;

    let mut clone_stm = Vec::<TokenStream2>::new();
    let first_pipe = is_first_pipe_char(body.as_slice());
    let body: TokenStream2 = body.iter().cloned().collect::<TokenStream>().into();

    for item in bind_params {
        let Some(name_param) = find_param_name(item) else {
            return Ok(quote! {""}.into());
        };

        clone_stm.push(quote!{
            let #name_param = #name_param.clone();
        });
    }

    let bind_result = if first_pipe {
        quote! {
            {
                #(#clone_stm)*

                move #body
            }
        }
    } else {
        quote! {
            {
                #(#clone_stm)*

                #body
            }
        }
    };

    Ok(bind_result.into())

}

fn convert_tokens_to_stream(tokens: &[TokenTree]) -> TokenStream2 {
    tokens
        .iter()
        .cloned()
        .collect::<TokenStream>()
        .into()
}

pub fn bind_spawn_fn(input: TokenStream) -> Result<TokenStream, String> {
    let tokens = input.into_iter().collect::<Vec<_>>();

    let TokensParamsBody { bind_params, func_params: _, body } = split_params_and_body(tokens.as_slice())?;

    let bind_params: Vec<TokenStream2> = bind_params
        .into_iter()
        .map(convert_tokens_to_stream)
        .collect::<Vec<_>>();

    let body: TokenStream2 = convert_tokens_to_stream(body.as_slice());

    Ok(quote! {
        {
            vertigo::bind!(#(#bind_params,)* || {
                vertigo::get_driver().spawn(vertigo::bind!(#(#bind_params,)* #body));
            })
        }
    }.into())
}

fn get_type(tokens: &[TokenTree]) -> Result<&[TokenTree], String> {
    let mut tokens = tokens
        .split(|token| {
            is_char(token, ':')
        })
        .collect::<VecDeque<_>>();

    if tokens.len() != 2 {
        return Err("type must be specified for all function parameters".to_string());
    }

    let _ = tokens.pop_front().unwrap();
    let type_tokens = tokens.pop_front().unwrap();

    Ok(type_tokens)
}

pub fn bind_rc_fn(input: TokenStream) -> Result<TokenStream, String> {
    let tokens = input.into_iter().collect::<Vec<_>>();

    let TokensParamsBody { bind_params, func_params, body } = split_params_and_body(tokens.as_slice())?;
    let bind_params: Vec<TokenStream2> = bind_params
        .into_iter()
        .map(convert_tokens_to_stream)
        .collect::<Vec<_>>();

    let Some(func_params) = func_params else {
        return Err("The macro can only take functions".to_string());
    };

    let types = {
        let mut types_macro: Vec<TokenStream2> = Vec::new();
        
        for type_item in func_params.into_iter() {
            let type_item = get_type(type_item)?;

            let type_item = convert_tokens_to_stream(type_item);
            types_macro.push(type_item);
        }

        types_macro
    };

    let body: TokenStream2 = convert_tokens_to_stream(body.as_slice());

    Ok(quote!{
        {
            let func: std::rc::Rc::<dyn Fn(#(#types,)*)> = std::rc::Rc::new(vertigo::bind!(#(#bind_params,)* #body));
            func
        }
    }.into())
}