use crate::children::Children;
use crate::element::Element;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result};
pub enum Child {
Element(Element),
RawBlock(syn::Block),
UnquotedText(String, proc_macro2::Span),
TextDirective(syn::Block),
HtmlDirective(syn::Block),
IfBlock(IfBlock),
ForBlock(ForBlock),
MatchBlock(MatchBlock),
LetStatement(syn::Stmt),
TailwindStyle(Vec<String>),
}
pub struct IfBlock {
pub condition: proc_macro2::TokenStream,
pub children: Children,
pub else_branch: Option<ElseBranch>,
}
pub enum ElseBranch {
ElseIf(Box<IfBlock>),
Else(Children),
}
pub struct ForBlock {
pub pattern: proc_macro2::TokenStream,
pub iter_expr: proc_macro2::TokenStream,
pub children: Children,
}
pub struct MatchBlock {
pub expr: proc_macro2::TokenStream,
pub arms: Vec<MatchArm>,
}
pub struct MatchArm {
pub pattern: proc_macro2::TokenStream,
pub guard: Option<proc_macro2::TokenStream>,
pub children: Children,
}
impl Child {
pub fn collect_class_names_from(child: &Child, classes: &mut Vec<String>) {
match child {
Child::Element(element) => {
classes.extend(element.collect_class_names());
}
Child::IfBlock(if_block) => {
Self::collect_from_if_block(if_block, classes);
}
Child::ForBlock(for_block) => {
for c in &for_block.children.nodes {
Self::collect_class_names_from(c, classes);
}
}
Child::MatchBlock(match_block) => {
for arm in &match_block.arms {
for c in &arm.children.nodes {
Self::collect_class_names_from(c, classes);
}
}
}
_ => {}
}
}
fn collect_from_if_block(if_block: &IfBlock, classes: &mut Vec<String>) {
for c in &if_block.children.nodes {
Self::collect_class_names_from(c, classes);
}
if let Some(else_branch) = &if_block.else_branch {
match else_branch {
ElseBranch::ElseIf(nested) => Self::collect_from_if_block(nested, classes),
ElseBranch::Else(children) => {
for c in &children.nodes {
Self::collect_class_names_from(c, classes);
}
}
}
}
}
}
fn parse_tokens_until_brace(input: ParseStream) -> Result<proc_macro2::TokenStream> {
let mut tokens = proc_macro2::TokenStream::new();
while !input.peek(syn::token::Brace) {
let token: proc_macro2::TokenTree = input.parse()?;
tokens.extend(std::iter::once(token));
}
Ok(tokens)
}
impl Parse for IfBlock {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<syn::Token![if]>()?;
let condition = parse_tokens_until_brace(input)?;
let content;
syn::braced!(content in input);
let children = Children::parse_until_empty(&content)?;
let else_branch = if input.peek(syn::Token![else]) {
input.parse::<syn::Token![else]>()?;
if input.peek(syn::Token![if]) {
Some(ElseBranch::ElseIf(Box::new(input.parse::<IfBlock>()?)))
} else {
let content;
syn::braced!(content in input);
let else_children = Children::parse_until_empty(&content)?;
Some(ElseBranch::Else(else_children))
}
} else {
None
};
Ok(IfBlock {
condition,
children,
else_branch,
})
}
}
impl Parse for ForBlock {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<syn::Token![for]>()?;
let mut pattern = proc_macro2::TokenStream::new();
while !input.peek(syn::Token![in]) {
let token: proc_macro2::TokenTree = input.parse()?;
pattern.extend(std::iter::once(token));
}
input.parse::<syn::Token![in]>()?;
let iter_expr = parse_tokens_until_brace(input)?;
let content;
syn::braced!(content in input);
let children = Children::parse_until_empty(&content)?;
Ok(ForBlock {
pattern,
iter_expr,
children,
})
}
}
impl Parse for MatchBlock {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<syn::Token![match]>()?;
let expr = parse_tokens_until_brace(input)?;
let match_content;
syn::braced!(match_content in input);
let mut arms = Vec::new();
while !match_content.is_empty() {
let mut arm_pattern = proc_macro2::TokenStream::new();
while !match_content.peek(syn::Token![=>])
&& !(match_content.peek(syn::Token![if])
&& !match_content.peek2(syn::Token![let]))
{
let token: proc_macro2::TokenTree = match_content.parse()?;
arm_pattern.extend(std::iter::once(token));
}
let guard = if match_content.peek(syn::Token![if]) {
match_content.parse::<syn::Token![if]>()?;
let mut guard_tokens = proc_macro2::TokenStream::new();
while !match_content.peek(syn::Token![=>]) {
let token: proc_macro2::TokenTree = match_content.parse()?;
guard_tokens.extend(std::iter::once(token));
}
Some(guard_tokens)
} else {
None
};
match_content.parse::<syn::Token![=>]>()?;
let arm_content;
syn::braced!(arm_content in match_content);
let arm_children = Children::parse_until_empty(&arm_content)?;
let _ = match_content.parse::<syn::Token![,]>();
arms.push(MatchArm {
pattern: arm_pattern,
guard,
children: arm_children,
});
}
Ok(MatchBlock { expr, arms })
}
}
impl IfBlock {
fn render_tokens(&self, buf_ident: &syn::Ident) -> proc_macro2::TokenStream {
let condition = &self.condition;
let children_tuple = self.children.as_tuples_tokens();
match &self.else_branch {
None => {
quote! {
if #condition {
workers_rsx::Render::render_into(#children_tuple, &mut #buf_ident).unwrap();
}
}
}
Some(ElseBranch::Else(else_children)) => {
let else_tuple = else_children.as_tuples_tokens();
quote! {
if #condition {
workers_rsx::Render::render_into(#children_tuple, &mut #buf_ident).unwrap();
} else {
workers_rsx::Render::render_into(#else_tuple, &mut #buf_ident).unwrap();
}
}
}
Some(ElseBranch::ElseIf(else_if_block)) => {
let else_if_render = else_if_block.render_tokens(buf_ident);
quote! {
if #condition {
workers_rsx::Render::render_into(#children_tuple, &mut #buf_ident).unwrap();
} else {
#else_if_render
}
}
}
}
}
}
impl ToTokens for Child {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Self::Element(element) => element.to_tokens(tokens),
Self::RawBlock(block) => {
let ts = if block.stmts.len() == 1 {
let first = &block.stmts[0];
quote!(#first)
} else {
quote!(#block)
};
ts.to_tokens(tokens);
}
Self::UnquotedText(text, _span) => {
let lit = syn::LitStr::new(text, proc_macro2::Span::call_site());
lit.to_tokens(tokens);
}
Self::TextDirective(block) => {
let ts = if block.stmts.len() == 1 {
let first = &block.stmts[0];
quote!(#first)
} else {
quote!(#block)
};
let result = quote! {{
let __text_val = #ts;
format!("{}", __text_val)
}};
result.to_tokens(tokens);
}
Self::HtmlDirective(block) => {
let ts = if block.stmts.len() == 1 {
let first = &block.stmts[0];
quote!(#first)
} else {
quote!(#block)
};
let result = quote! {
workers_rsx::RawOwned(format!("{}", #ts))
};
result.to_tokens(tokens);
}
Self::IfBlock(if_block) => {
let buf_ident =
syn::Ident::new("__rsx_buf", proc_macro2::Span::call_site());
let render_code = if_block.render_tokens(&buf_ident);
let result = quote! {{
let mut #buf_ident = String::new();
#render_code
workers_rsx::RawOwned(#buf_ident)
}};
result.to_tokens(tokens);
}
Self::ForBlock(for_block) => {
let buf_ident =
syn::Ident::new("__rsx_buf", proc_macro2::Span::call_site());
let pattern = &for_block.pattern;
let iter_expr = &for_block.iter_expr;
let children_tuple = for_block.children.as_tuples_tokens();
let result = quote! {{
let mut #buf_ident = String::new();
for #pattern in #iter_expr {
workers_rsx::Render::render_into(#children_tuple, &mut #buf_ident).unwrap();
}
workers_rsx::RawOwned(#buf_ident)
}};
result.to_tokens(tokens);
}
Self::MatchBlock(match_block) => {
let buf_ident =
syn::Ident::new("__rsx_buf", proc_macro2::Span::call_site());
let expr = &match_block.expr;
let arms: Vec<_> = match_block
.arms
.iter()
.map(|arm| {
let pattern = &arm.pattern;
let guard = &arm.guard;
let children_tuple = arm.children.as_tuples_tokens();
match guard {
Some(guard_expr) => quote! {
#pattern if #guard_expr => {
workers_rsx::Render::render_into(#children_tuple, &mut #buf_ident).unwrap();
}
},
None => quote! {
#pattern => {
workers_rsx::Render::render_into(#children_tuple, &mut #buf_ident).unwrap();
}
},
}
})
.collect();
let result = quote! {{
let mut #buf_ident = String::new();
match #expr {
#(#arms)*
}
workers_rsx::RawOwned(#buf_ident)
}};
result.to_tokens(tokens);
}
Self::LetStatement(stmt) => {
let result = quote! {{
#stmt
()
}};
result.to_tokens(tokens);
}
Self::TailwindStyle(class_names) => {
let class_lits: Vec<_> = class_names
.iter()
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
.collect();
let result = quote! {{
let __css = ::workers_rsx::generate_tailwind_css(&[#(#class_lits),*]);
if __css.is_empty() {
workers_rsx::RawOwned(String::new())
} else {
workers_rsx::RawOwned(format!("<style>{}</style>", __css))
}
}};
result.to_tokens(tokens);
}
}
}
}
impl Parse for Child {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(syn::Token![if]) {
Ok(Self::IfBlock(input.parse()?))
} else if input.peek(syn::Token![for]) {
Ok(Self::ForBlock(input.parse()?))
} else if input.peek(syn::Token![match]) {
Ok(Self::MatchBlock(input.parse()?))
} else if input.peek(syn::Token![let]) || input.peek(syn::Token![const]) {
let stmt: syn::Stmt = input.parse()?;
Ok(Self::LetStatement(stmt))
} else if input.peek(syn::token::Brace) {
let fork = input.fork();
let content;
syn::braced!(content in fork);
if content.peek(syn::Ident) {
let ident: syn::Ident = content.fork().parse()?;
if ident == "text" {
let content;
syn::braced!(content in input);
content.parse::<syn::Ident>()?; let inner_block = syn::Block {
brace_token: syn::token::Brace::default(),
stmts: vec![syn::Stmt::Expr(content.parse()?)],
};
return Ok(Self::TextDirective(inner_block));
} else if ident == "html" {
let content;
syn::braced!(content in input);
content.parse::<syn::Ident>()?; let inner_block = syn::Block {
brace_token: syn::token::Brace::default(),
stmts: vec![syn::Stmt::Expr(content.parse()?)],
};
return Ok(Self::HtmlDirective(inner_block));
}
}
let block = input.parse::<syn::Block>()?;
Ok(Self::RawBlock(block))
} else if input.peek(syn::Token![<]) {
match input.parse::<Element>() {
Ok(element) => Ok(Self::Element(element)),
Err(_) => {
let block = input.parse::<syn::Block>()?;
Ok(Self::RawBlock(block))
}
}
} else {
let mut text = String::new();
let span = input.span();
while !input.is_empty()
&& !input.peek(syn::Token![<])
&& !input.peek(syn::token::Brace)
{
if input.peek(syn::LitStr) {
let lit = input.parse::<syn::LitStr>()?;
text.push_str(&lit.value());
} else {
let tt: proc_macro2::TokenTree = input.parse()?;
let s = tt.to_string();
if !text.is_empty() {
let no_space_before = matches!(
s.as_str(),
"!" | "," | "." | "?" | ";" | ":" | "'" | ")" | "]"
);
if !no_space_before {
text.push(' ');
}
}
text.push_str(&s);
}
}
if text.is_empty() {
return Err(input.error("expected a child element, text, or expression"));
}
Ok(Self::UnquotedText(text, span))
}
}
}