use super::*; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ ext::IdentExt, parse::{discouraged::Speculative, Parse, ParseBuffer, ParseStream}, token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token, }; // ======================================= // Parse the VNode::Element type // ======================================= pub struct Element { name: Ident, key: Option, attributes: Vec, listeners: Vec, children: Vec, is_static: bool, } impl ToTokens for Element { fn to_tokens(&self, tokens: &mut TokenStream2) { let name = &self.name; let attr = &self.attributes; let childs = &self.children; let listeners = &self.listeners; tokens.append_all(quote! { __cx.element( dioxus_elements::#name, __cx.bump().alloc([ #(#listeners),* ]), __cx.bump().alloc([ #(#attr),* ]), __cx.bump().alloc([ #(#childs),* ]), None, ) }); } } impl Parse for Element { fn parse(stream: ParseStream) -> Result { let name = Ident::parse(stream)?; // parse the guts let content: ParseBuffer; syn::braced!(content in stream); let mut attributes: Vec = vec![]; let mut listeners: Vec = vec![]; let mut children: Vec = vec![]; let mut key = None; 'parsing: loop { // [1] Break if empty if content.is_empty() { break 'parsing; } if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) { parse_element_body( &content, &mut attributes, &mut listeners, &mut key, name.clone(), )?; } else { children.push(content.parse::()?); } // consume comma if it exists // we don't actually care if there *are* commas after elements/text if content.peek(Token![,]) { let _ = content.parse::(); } } Ok(Self { key, name, attributes, children, listeners, is_static: false, }) } } /// ======================================= /// Parse a VElement's Attributes /// ======================================= struct ElementAttr { element_name: Ident, name: Ident, value: AttrType, namespace: Option, } enum AttrType { BumpText(LitStr), FieldTokens(Expr), EventTokens(Expr), Event(ExprClosure), } // We parse attributes and dump them into the attribute vec // This is because some tags might be namespaced (IE style) // These dedicated tags produce multiple name-spaced attributes fn parse_element_body( stream: ParseStream, attrs: &mut Vec, listeners: &mut Vec, key: &mut Option, element_name: Ident, ) -> Result<()> { let mut name = Ident::parse_any(stream)?; let name_str = name.to_string(); stream.parse::()?; // Return early if the field is a listener if name_str.starts_with("on") { // remove the "on" bit // name = Ident::new(&name_str.trim_start_matches("on"), name.span()); let ty = if stream.peek(token::Brace) { let content; syn::braced!(content in stream); // Try to parse directly as a closure let fork = content.fork(); if let Ok(event) = fork.parse::() { content.advance_to(&fork); AttrType::Event(event) } else { AttrType::EventTokens(content.parse()?) } } else { AttrType::Event(stream.parse()?) }; listeners.push(ElementAttr { name, value: ty, namespace: None, element_name: element_name.clone(), }); return Ok(()); } let ty: AttrType = match name_str.as_str() { // short circuit early if style is using the special syntax "style" if stream.peek(token::Brace) => { let inner; syn::braced!(inner in stream); while !inner.is_empty() { let name = Ident::parse_any(&inner)?; inner.parse::()?; let ty = if inner.peek(LitStr) { let rawtext = inner.parse::().unwrap(); AttrType::BumpText(rawtext) } else { let toks = inner.parse::()?; AttrType::FieldTokens(toks) }; if inner.peek(Token![,]) { let _ = inner.parse::(); } attrs.push(ElementAttr { name, value: ty, namespace: Some("style".to_string()), element_name: element_name.clone(), }); } return Ok(()); } "key" => { *key = Some(AttrType::BumpText(stream.parse::()?)); return Ok(()); } "classes" => { todo!("custom class lsit not supported") } "namespace" => { todo!("custom namespace not supported") } "ref" => { todo!("NodeRefs are currently not supported! This is currently a reserved keyword.") } // Fall through _ => { if stream.peek(LitStr) { let rawtext = stream.parse::().unwrap(); AttrType::BumpText(rawtext) } else { let toks = stream.parse::()?; AttrType::FieldTokens(toks) } } }; // consume comma if it exists // we don't actually care if there *are* commas between attrs if stream.peek(Token![,]) { let _ = stream.parse::(); } attrs.push(ElementAttr { name, value: ty, namespace: None, element_name, }); Ok(()) } impl ToTokens for ElementAttr { fn to_tokens(&self, tokens: &mut TokenStream2) { let el_name = &self.element_name; let name_str = self.name.to_string(); let nameident = &self.name; let namespace = match &self.namespace { Some(t) => quote! { Some(#t) }, None => quote! { None }, }; match &self.value { AttrType::BumpText(value) => tokens.append_all(quote! { dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value)) }), AttrType::FieldTokens(exp) => tokens.append_all(quote! { dioxus_elements::#el_name.#nameident(__cx, #exp) }), // todo: move event handlers on to the elements or onto the nodefactory AttrType::Event(event) => tokens.append_all(quote! { dioxus::events::on::#nameident(__cx, #event) }), AttrType::EventTokens(event) => tokens.append_all(quote! { dioxus::events::on::#nameident(__cx, #event) }), } } } // __cx.attr(#name, format_args_f!(#value), #namespace, false) // // AttrType::BumpText(value) => tokens.append_all(quote! { // __cx.attr(#name, format_args_f!(#value), #namespace, false) // }), // __cx.attr(#name_str, #exp, #namespace, false) // AttrType::FieldTokens(exp) => tokens.append_all(quote! { // dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value)) // __cx.attr(#name_str, #exp, #namespace, false) // }),