use proc_macro2::{Ident, Span, TokenStream as TokenStream2, TokenTree};
use quote::quote;
use std::{
collections::{HashSet, VecDeque},
error::Error,
};
use syn::__private::quote::format_ident;
pub(crate) fn bind_inner(input: TokenStream2) -> Option<TokenStream2> {
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 = body.iter().cloned().collect::<TokenStream2>();
let mut idents_seen = HashSet::new();
for item in bind_params {
let param_name = find_param_name(item)?;
if !idents_seen.insert(param_name.clone()) {
emit_error!(
item.last()
.map(|i| i.span())
.unwrap_or_else(Span::call_site),
"Conflicting variable name: {}",
param_name
);
}
let item_expr = item.iter().cloned().collect::<TokenStream2>();
clone_stm.push(quote! {
let #param_name = #item_expr.clone();
});
}
let bind_result = if first_pipe {
quote! {
{
#(#clone_stm)*
move #body
}
}
} else {
quote! {
{
#(#clone_stm)*
#body
}
}
};
Some(bind_result)
}
pub(crate) fn bind_spawn_inner(input: TokenStream2) -> Option<TokenStream2> {
let tokens = input.into_iter().collect::<Vec<_>>();
let TokensParamsBody {
bind_params,
func_params,
mut 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 func_params: Vec<TokenStream2> = func_params
.unwrap_or_default()
.into_iter()
.map(convert_tokens_to_stream)
.collect::<Vec<_>>();
let mut start_from = 0;
let mut pipes = 0;
for (i, token) in body.clone().into_iter().enumerate() {
if let TokenTree::Punct(punct) = token {
if punct.as_char() == '{' {
break;
}
if punct.as_char() == '|' {
pipes += 1;
}
if pipes >= 2 {
start_from = i + 1;
break;
}
}
}
if start_from > 0 {
body = body.splice(start_from.., []).collect();
}
let inner_body: TokenStream2 = TokenStream2::from_iter(body);
Some(quote! {
{
vertigo::bind!(#(#bind_params,)* |#(#func_params,)*| {
vertigo::get_driver().spawn(vertigo::bind!(#(#bind_params,)* #inner_body));
})
}
})
}
pub(crate) fn bind_rc_inner(input: TokenStream2) -> Option<TokenStream2> {
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 {
emit_call_site_error!("The macro can only take functions");
return None;
};
let types = {
let mut types_macro: Vec<TokenStream2> = Vec::new();
for type_items in func_params.into_iter() {
let Ok(type_item) = get_type(type_items) else {
emit_error!(
type_items
.first()
.map(|i| i.span())
.unwrap_or_else(Span::call_site),
"The macro can only take functions"
);
return None;
};
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());
Some(quote! {
{
let func: std::rc::Rc::<dyn Fn(#(#types,)*) -> _> = std::rc::Rc::new(vertigo::bind!(#(#bind_params,)* #body));
func
}
})
}
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<Ident> {
if let Some(last) = params.last() {
if let TokenTree::Ident(value) = &last {
Some(format_ident!("{}", value.to_string()))
} else {
emit_error!(
Span::call_site(),
"Can't find variable name, expected ident (1)"
);
None
}
} else {
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 contains_bracket(tokens: &[TokenTree]) -> bool {
for token in tokens {
if let TokenTree::Punct(inner) = token
&& inner.as_char() == '|'
{
return true;
}
}
false
}
fn split_params_and_body_function(tokens: &'_ [TokenTree]) -> Option<TokensParamsBody<'_>> {
let mut chunks = tokens
.split(|token| is_char(token, '|'))
.collect::<VecDeque<_>>();
if chunks.len() > 3 {
emit_error!(tokens[3].span(), "Too many brackets '|' (2 were expected)");
return None;
}
let Some(params_chunk) = chunks.pop_front() else {
emit_call_site_error!("Two brackets '|' were expected");
return None;
};
let bind_params = params_chunk
.split(|token| is_char(token, ','))
.filter(|item| !item.is_empty())
.collect::<Vec<_>>();
let Some(func_params) = chunks.pop_front() else {
emit_error!(tokens[0].span(), "Two brackets '|' were expected");
return None;
};
let func_params = func_params
.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
};
Some(TokensParamsBody {
bind_params,
func_params: Some(func_params),
body,
})
}
fn split_params_and_body_block(tokens: &[TokenTree]) -> Option<TokensParamsBody<'_>> {
let mut chunks = tokens
.split(|token| is_char(token, ','))
.collect::<Vec<_>>();
let Some(body) = chunks.pop() else {
emit_call_site_error!("Two brackets '|' were expected");
return None;
};
Some(TokensParamsBody {
bind_params: chunks,
func_params: None,
body: body.to_vec(),
})
}
fn split_params_and_body(tokens: &[TokenTree]) -> Option<TokensParamsBody<'_>> {
let bracket_contain = contains_bracket(tokens);
if bracket_contain {
split_params_and_body_function(tokens)
} else {
split_params_and_body_block(tokens)
}
}
fn convert_tokens_to_stream(tokens: &[TokenTree]) -> TokenStream2 {
tokens.iter().cloned().collect::<TokenStream2>()
}
fn get_type(tokens: &[TokenTree]) -> Result<&[TokenTree], Box<dyn Error>> {
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".into());
}
let _ = tokens.pop_front().ok_or("unreachable (1)")?;
let type_tokens = tokens.pop_front().ok_or("unreachable (2)")?;
Ok(type_tokens)
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
use std::error::Error;
fn format_token_stream(tokens: TokenStream2) -> Result<String, Box<dyn Error>> {
let file = syn::parse2::<syn::File>(quote! {
fn dummy() {
#tokens
}
})?;
Ok(prettyplease::unparse(&file))
}
#[test]
fn test_is_char() {
let punct = TokenTree::Punct(proc_macro2::Punct::new('|', proc_macro2::Spacing::Alone));
assert!(is_char(&punct, '|'));
assert!(!is_char(&punct, ','));
let ident = TokenTree::Ident(proc_macro2::Ident::new("foo", Span::call_site()));
assert!(!is_char(&ident, '|'));
}
#[test]
fn test_find_param_name() -> Result<(), Box<dyn Error>> {
let tokens = vec![TokenTree::Ident(proc_macro2::Ident::new(
"foo",
Span::call_site(),
))];
let name = find_param_name(&tokens).ok_or("find_param_name returned None")?;
assert_eq!(name.to_string(), "foo");
let tokens = vec![
TokenTree::Punct(proc_macro2::Punct::new('&', proc_macro2::Spacing::Alone)),
TokenTree::Ident(proc_macro2::Ident::new("bar", Span::call_site())),
];
let name = find_param_name(&tokens).ok_or("find_param_name returned None")?;
assert_eq!(name.to_string(), "bar");
Ok(())
}
#[test]
fn test_is_first_pipe_char() {
let tokens = vec![TokenTree::Punct(proc_macro2::Punct::new(
'|',
proc_macro2::Spacing::Alone,
))];
assert!(is_first_pipe_char(&tokens));
let tokens = vec![TokenTree::Ident(proc_macro2::Ident::new(
"foo",
Span::call_site(),
))];
assert!(!is_first_pipe_char(&tokens));
}
#[test]
fn test_contains_bracket() {
let tokens = vec![
TokenTree::Ident(proc_macro2::Ident::new("foo", Span::call_site())),
TokenTree::Punct(proc_macro2::Punct::new('|', proc_macro2::Spacing::Alone)),
];
assert!(contains_bracket(&tokens));
let tokens = vec![TokenTree::Ident(proc_macro2::Ident::new(
"foo",
Span::call_site(),
))];
assert!(!contains_bracket(&tokens));
}
#[test]
fn test_get_type() -> Result<(), Box<dyn Error>> {
let tokens = vec![
TokenTree::Ident(proc_macro2::Ident::new("val", Span::call_site())),
TokenTree::Punct(proc_macro2::Punct::new(':', proc_macro2::Spacing::Alone)),
TokenTree::Ident(proc_macro2::Ident::new("String", Span::call_site())),
];
let res = get_type(&tokens)?;
assert_eq!(res.len(), 1);
if let TokenTree::Ident(ident) = &res[0] {
assert_eq!(ident.to_string(), "String");
} else {
return Err("Expected ident".into());
}
Ok(())
}
#[test]
fn test_split_params_and_body_block() -> Result<(), Box<dyn Error>> {
let input = quote! { a, b, { body } };
let tokens = input.into_iter().collect::<Vec<_>>();
let res = split_params_and_body(&tokens).ok_or("split_params_and_body returned None")?;
assert_eq!(res.bind_params.len(), 2);
assert!(res.func_params.is_none());
Ok(())
}
#[test]
fn test_split_params_and_body_function() -> Result<(), Box<dyn Error>> {
let input = quote! { a, b | c: i32 | { body } };
let tokens = input.into_iter().collect::<Vec<_>>();
let res = split_params_and_body(&tokens).ok_or("split_params_and_body returned None")?;
assert_eq!(res.bind_params.len(), 2);
assert_eq!(res.func_params.ok_or("unreachable")?.len(), 1);
Ok(())
}
#[test]
fn test_bind_inner_simple() -> Result<(), Box<dyn Error>> {
let input = quote! { { println!("hello") } };
let result = bind_inner(input).ok_or("bind_inner returned None")?;
let expected = quote! {
{
{ println!("hello") }
}
};
pretty_assertions::assert_eq!(format_token_stream(result)?, format_token_stream(expected)?);
Ok(())
}
#[test]
fn test_bind_inner_with_params() -> Result<(), Box<dyn Error>> {
let input = quote! { a, b, { println!("hello {} {}", a, b) } };
let result = bind_inner(input).ok_or("bind_inner returned None")?;
let expected = quote! {
{
let a = a.clone();
let b = b.clone();
{ println!("hello {} {}", a, b) }
}
};
pretty_assertions::assert_eq!(format_token_stream(result)?, format_token_stream(expected)?);
Ok(())
}
#[test]
fn test_bind_inner_closure() -> Result<(), Box<dyn Error>> {
let input = quote! { a |param| { println!("hello {} {}", a, param) } };
let result = bind_inner(input).ok_or("bind_inner returned None")?;
let expected = quote! {
{
let a = a.clone();
move |param| { println!("hello {} {}", a, param) }
}
};
pretty_assertions::assert_eq!(format_token_stream(result)?, format_token_stream(expected)?);
Ok(())
}
#[test]
fn test_bind_spawn_inner() -> Result<(), Box<dyn Error>> {
let input = quote! { a, b | | { println!("hello") } };
let result = bind_spawn_inner(input).ok_or("bind_spawn_inner returned None")?;
let expected = quote! {
{
vertigo::bind!(a, b, | | {
vertigo::get_driver().spawn(vertigo::bind!(a, b, { println!("hello") }));
})
}
};
pretty_assertions::assert_eq!(format_token_stream(result)?, format_token_stream(expected)?);
Ok(())
}
#[test]
fn test_bind_rc_inner() -> Result<(), Box<dyn Error>> {
let input = quote! { a | b: i32 | { println!("hello {} {}", a, b) } };
let result = bind_rc_inner(input).ok_or("bind_rc_inner returned None")?;
let expected = quote! {
{
let func: std::rc::Rc::<dyn Fn(i32) -> _> = std::rc::Rc::new(
vertigo::bind!(a, |b: i32| { println!("hello {} {}", a, b) })
);
func
}
};
pretty_assertions::assert_eq!(format_token_stream(result)?, format_token_stream(expected)?);
Ok(())
}
}