use self::location::CallerLocation;
use super::*;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
braced,
parse::{Parse, ParseStream},
spanned::Spanned,
token::{self, Brace},
Expr, ExprIf, LitStr, Pat, Result,
};
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum BodyNode {
Element(Element),
Text(IfmtInput),
RawExpr(Expr),
Component(Component),
ForLoop(ForLoop),
IfChain(IfChain),
}
impl BodyNode {
pub fn is_litstr(&self) -> bool {
matches!(self, BodyNode::Text { .. })
}
pub fn span(&self) -> Span {
match self {
BodyNode::Element(el) => el.name.span(),
BodyNode::Component(component) => component.name.span(),
BodyNode::Text(text) => text.source.span(),
BodyNode::RawExpr(exp) => exp.span(),
BodyNode::ForLoop(fl) => fl.for_token.span(),
BodyNode::IfChain(f) => f.if_token.span(),
}
}
}
impl Parse for BodyNode {
fn parse(stream: ParseStream) -> Result<Self> {
if stream.peek(LitStr) {
return Ok(BodyNode::Text(stream.parse()?));
}
let body_stream = stream.fork();
if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
if name.value().contains('-') && body_stream.peek(token::Brace) {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
let body_stream = stream.fork();
if let Ok(path) = body_stream.parse::<syn::Path>() {
if let Some(ident) = path.get_ident() {
let el_name = ident.to_string();
let first_char = el_name.chars().next().unwrap();
if body_stream.peek(token::Brace)
&& first_char.is_ascii_lowercase()
&& !el_name.contains('_')
{
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
if body_stream.peek(token::Brace) {
return Ok(BodyNode::Component(stream.parse()?));
}
}
if stream.peek(Token![for]) {
return Ok(BodyNode::ForLoop(stream.parse()?));
}
if stream.peek(Token![if]) {
return Ok(BodyNode::IfChain(stream.parse()?));
}
if stream.peek(Token![match]) {
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
}
if stream.peek(token::Brace) {
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
}
Err(syn::Error::new(
stream.span(),
"Expected a valid body node.\nExpressions must be wrapped in curly braces.",
))
}
}
impl ToTokens for BodyNode {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
BodyNode::Element(_) => {
unimplemented!("Elements are statically created in the template")
}
BodyNode::Text(txt) => tokens.append_all(quote! {
dioxus_core::DynamicNode::Text(dioxus_core::VText::new(#txt.to_string()))
}),
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
{
let ___nodes = (#exp).into_dyn_node();
___nodes
}
}),
BodyNode::Component(comp) => tokens.append_all(quote! { #comp }),
BodyNode::ForLoop(exp) => tokens.append_all(quote! { #exp }),
BodyNode::IfChain(chain) => tokens.append_all(quote! { #chain }),
}
}
}
#[non_exhaustive]
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct ForLoop {
pub for_token: Token![for],
pub pat: Pat,
pub in_token: Token![in],
pub expr: Box<Expr>,
pub body: Vec<BodyNode>,
pub brace_token: token::Brace,
pub location: CallerLocation,
}
impl Parse for ForLoop {
fn parse(input: ParseStream) -> Result<Self> {
let for_token: Token![for] = input.parse()?;
let pat = Pat::parse_single(input)?;
let in_token: Token![in] = input.parse()?;
let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
let (brace_token, body) = parse_buffer_as_braced_children(input)?;
Ok(Self {
for_token,
pat,
in_token,
body,
brace_token,
location: CallerLocation::default(),
expr: Box::new(expr),
})
}
}
impl ToTokens for ForLoop {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ForLoop {
pat, expr, body, ..
} = self;
let renderer = TemplateRenderer::as_tokens(body, None);
tokens.append_all(quote! {
{
let ___nodes = (#expr).into_iter().map(|#pat| { #renderer }).into_dyn_node();
___nodes
}
})
}
}
#[non_exhaustive]
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct IfChain {
pub if_token: Token![if],
pub cond: Box<Expr>,
pub then_branch: Vec<BodyNode>,
pub else_if_branch: Option<Box<IfChain>>,
pub else_branch: Option<Vec<BodyNode>>,
pub location: CallerLocation,
}
impl Parse for IfChain {
fn parse(input: ParseStream) -> Result<Self> {
let if_token: Token![if] = input.parse()?;
let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
let (_, then_branch) = parse_buffer_as_braced_children(input)?;
let mut else_branch = None;
let mut else_if_branch = None;
if input.peek(Token![else]) {
input.parse::<Token![else]>()?;
if input.peek(Token![if]) {
else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
} else {
let (_, else_branch_nodes) = parse_buffer_as_braced_children(input)?;
else_branch = Some(else_branch_nodes);
}
}
Ok(Self {
cond,
if_token,
then_branch,
else_if_branch,
else_branch,
location: CallerLocation::default(),
})
}
}
impl ToTokens for IfChain {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let mut body = TokenStream2::new();
let mut terminated = false;
let mut elif = Some(self);
while let Some(chain) = elif {
let IfChain {
if_token,
cond,
then_branch,
else_if_branch,
else_branch,
..
} = chain;
let renderer = TemplateRenderer::as_tokens(then_branch, None);
body.append_all(quote! { #if_token #cond { Some({#renderer}) } });
if let Some(next) = else_if_branch {
body.append_all(quote! { else });
elif = Some(next);
} else if let Some(else_branch) = else_branch {
let renderer = TemplateRenderer::as_tokens(else_branch, None);
body.append_all(quote! { else { Some({#renderer}) } });
terminated = true;
break;
} else {
elif = None;
}
}
if !terminated {
body.append_all(quote! {
else { None }
});
}
tokens.append_all(quote! {
{
let ___nodes = (#body).into_dyn_node();
___nodes
}
})
}
}
fn parse_buffer_as_braced_children(
input: &syn::parse::ParseBuffer<'_>,
) -> Result<(Brace, Vec<BodyNode>)> {
let content;
let brace_token = braced!(content in input);
let mut then_branch = vec![];
while !content.is_empty() {
then_branch.push(content.parse()?);
}
Ok((brace_token, then_branch))
}
pub(crate) fn is_if_chain_terminated(chain: &ExprIf) -> bool {
let mut current = chain;
loop {
if let Some((_, else_block)) = ¤t.else_branch {
if let Expr::If(else_if) = else_block.as_ref() {
current = else_if;
} else {
return true;
}
} else {
return false;
}
}
}