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 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; let mut el_ref = None; 'parsing: loop { // [1] Break if empty if content.is_empty() { break 'parsing; } if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) { parse_rsx_element_field( &content, &mut attributes, &mut listeners, &mut key, &mut el_ref, 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, }) } } impl Parse for Element { fn parse(stream: ParseStream) -> Result { let _l_tok = stream.parse::()?; let el_name = Ident::parse(stream)?; let mut attributes: Vec> = vec![]; let mut listeners: Vec> = vec![]; let mut children: Vec> = vec![]; let key = None; while !stream.peek(Token![>]) { // self-closing if stream.peek(Token![/]) { stream.parse::()?; stream.parse::]>()?; return Ok(Self { name: el_name, key: None, attributes, _is_static: false, listeners, children, }); } let name = Ident::parse_any(stream)?; let name_str = name.to_string(); stream.parse::()?; if name_str.starts_with("on") { let inner; syn::braced!(inner in stream); let toks = inner.parse::()?; let ty = AttrType::EventTokens(toks); listeners.push(ElementAttr { element_name: el_name.clone(), name, value: ty, namespace: None, }) } else { match name_str.as_str() { "style" => {} "key" => {} _ => { // "classes" | "namespace" | "ref" | _ => { let ty = if stream.peek(LitStr) { let rawtext = stream.parse::().unwrap(); AttrType::BumpText(rawtext) } else { // like JSX, we expect raw expressions let inner; syn::braced!(inner in stream); let toks = inner.parse::()?; AttrType::FieldTokens(toks) }; attributes.push(ElementAttr { element_name: el_name.clone(), name, value: ty, namespace: None, }) } } }; } stream.parse::]>()?; 'parsing: loop { if stream.peek(Token![<]) && stream.peek2(Token![/]) { break 'parsing; } // [1] Break if empty if stream.is_empty() { break 'parsing; } children.push(stream.parse::>()?); } // closing element stream.parse::()?; stream.parse::()?; let close = Ident::parse_any(stream)?; if close != el_name { return Err(Error::new_spanned( close, "closing element does not match opening", )); } stream.parse::]>()?; Ok(Self { key, name: el_name, attributes, children, listeners, _is_static: false, }) } } 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; let key = match &self.key { Some(ty) => quote! { Some(format_args_f!(#ty)) }, None => quote! { None }, }; tokens.append_all(quote! { __cx.element( dioxus_elements::#name, [ #(#listeners),* ], [ #(#attr),* ], [ #(#childs),* ], #key, ) }); } } /// ======================================= /// 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_rsx_element_field( stream: ParseStream, attrs: &mut Vec>, listeners: &mut Vec>, key: &mut Option, el_ref: &mut Option, element_name: Ident, ) -> Result<()> { let 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 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, }); 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(stream.parse::()?); return Ok(()); } "classes" => { todo!("custom class list not supported") } "namespace" => { todo!("custom namespace not supported") } "node_ref" => { *el_ref = Some(stream.parse::()?); return Ok(()); } // 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 nameident = &self.name; // TODO: wire up namespace let _name_str = self.name.to_string(); 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) }), 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) }), } } }