//! //! TODO: //! - [ ] Support for VComponents //! - [ ] Support for inline format in text //! - [ ] Support for expressions in attribute positions //! - [ ] Support for iterators //! - [ ] support for inline html! //! //! //! //! //! //! //! use { proc_macro2::TokenStream as TokenStream2, quote::{quote, ToTokens, TokenStreamExt}, syn::{ ext::IdentExt, parse::{Parse, ParseStream}, token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token, }, }; // ============================================== // Parse any stream coming from the html! macro // ============================================== pub struct HtmlRender { kind: NodeOrList, } impl Parse for HtmlRender { fn parse(input: ParseStream) -> Result { if input.peek(LitStr) { return input.parse::()?.parse::(); } // let __cx: Ident = s.parse()?; // s.parse::()?; // if elements are in an array, return a bumpalo::collections::Vec rather than a Node. let kind = if input.peek(token::Bracket) { let nodes_toks; syn::bracketed!(nodes_toks in input); let mut nodes: Vec> = vec![nodes_toks.parse()?]; while nodes_toks.peek(Token![,]) { nodes_toks.parse::()?; nodes.push(nodes_toks.parse()?); } NodeOrList::List(NodeList(nodes)) } else { NodeOrList::Node(input.parse()?) }; Ok(HtmlRender { kind }) } } impl ToTokens for HtmlRender { fn to_tokens(&self, out_tokens: &mut TokenStream2) { let new_toks = ToToksCtx::new(&self.kind).to_token_stream(); // create a lazy tree that accepts a bump allocator let final_tokens = quote! { dioxus::prelude::LazyNodes::new(move |__cx| { let bump = __cx.bump(); #new_toks }) }; final_tokens.to_tokens(out_tokens); } } /// ============================================= /// Parse any child as a node or list of nodes /// ============================================= /// - [ ] Allow iterators /// /// enum NodeOrList { Node(Node), List(NodeList), } impl ToTokens for ToToksCtx<&NodeOrList> { fn to_tokens(&self, tokens: &mut TokenStream2) { match self.inner { NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens), NodeOrList::List(list) => self.recurse(list).to_tokens(tokens), } } } struct NodeList(Vec>); impl ToTokens for ToToksCtx<&NodeList> { fn to_tokens(&self, tokens: &mut TokenStream2) { let nodes = self.inner.0.iter().map(|node| self.recurse(node)); tokens.append_all(quote! { dioxus::bumpalo::vec![in bump; #(#nodes),* ] }); } } enum Node { Element(Element), Text(TextNode), } impl ToTokens for ToToksCtx<&Node> { fn to_tokens(&self, tokens: &mut TokenStream2) { match &self.inner { Node::Element(el) => self.recurse(el).to_tokens(tokens), Node::Text(txt) => self.recurse(txt).to_tokens(tokens), } } } impl Node { fn _peek(s: ParseStream) -> bool { (s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr) } } impl Parse for Node { fn parse(s: ParseStream) -> Result { Ok(if s.peek(Token![<]) { Node::Element(s.parse()?) } else { Node::Text(s.parse()?) }) } } /// ======================================= /// Parse the VNode::Element type /// ======================================= /// - [ ] Allow VComponent /// /// struct Element { name: Ident, attrs: Vec, children: MaybeExpr>, } impl ToTokens for ToToksCtx<&Element> { fn to_tokens(&self, tokens: &mut TokenStream2) { // let __cx = self.__cx; let name = &self.inner.name; // let name = &self.inner.name.to_string(); tokens.append_all(quote! { __cx.element(dioxus_elements::#name) // dioxus::builder::ElementBuilder::new( #name) }); for attr in self.inner.attrs.iter() { self.recurse(attr).to_tokens(tokens); } // if is_valid_svg_tag(&name.to_string()) { // tokens.append_all(quote! { // .namespace(Some("http://www.w3.org/2000/svg")) // }); // } match &self.inner.children { MaybeExpr::Expr(expr) => tokens.append_all(quote! { .children(#expr) }), MaybeExpr::Literal(nodes) => { let mut children = nodes.iter(); if let Some(child) = children.next() { let mut inner_toks = TokenStream2::new(); self.recurse(child).to_tokens(&mut inner_toks); for child in children { quote!(,).to_tokens(&mut inner_toks); self.recurse(child).to_tokens(&mut inner_toks); } tokens.append_all(quote! { .children([#inner_toks]) }); } } } tokens.append_all(quote! { .finish() }); } } impl Parse for Element { fn parse(s: ParseStream) -> Result { s.parse::()?; let name = Ident::parse_any(s)?; let mut attrs = vec![]; let _children: Vec = vec![]; // keep looking for attributes while !s.peek(Token![>]) { // self-closing if s.peek(Token![/]) { s.parse::()?; s.parse::]>()?; return Ok(Self { name, attrs, children: MaybeExpr::Literal(vec![]), }); } attrs.push(s.parse()?); } s.parse::]>()?; // Contents of an element can either be a brace (in which case we just copy verbatim), or a // sequence of nodes. let children = if s.peek(token::Brace) { // expr let content; syn::braced!(content in s); MaybeExpr::Expr(content.parse()?) } else { // nodes let mut children = vec![]; while !(s.peek(Token![<]) && s.peek2(Token![/])) { children.push(s.parse()?); } MaybeExpr::Literal(children) }; // closing element s.parse::()?; s.parse::()?; let close = Ident::parse_any(s)?; if close != name { return Err(Error::new_spanned( close, "closing element does not match opening", )); } s.parse::]>()?; Ok(Self { name, attrs, children, }) } } /// ======================================= /// Parse a VElement's Attributes /// ======================================= /// - [ ] Allow expressions as attribute /// /// struct Attr { name: Ident, ty: AttrType, } impl Parse for Attr { fn parse(s: ParseStream) -> Result { let mut name = Ident::parse_any(s)?; let name_str = name.to_string(); s.parse::()?; // Check if this is an event handler // If so, parse into literal tokens let ty = if name_str.starts_with("on") { // remove the "on" bit name = Ident::new(name_str.trim_start_matches("on"), name.span()); let content; syn::braced!(content in s); // AttrType::Value(content.parse()?) AttrType::Event(content.parse()?) // AttrType::Event(content.parse()?) } else { let lit_str = if name_str == "style" && s.peek(token::Brace) { // special-case to deal with literal styles. let outer; syn::braced!(outer in s); // double brace for inline style. // todo!("Style support not ready yet"); // if outer.peek(token::Brace) { // let inner; // syn::braced!(inner in outer); // let styles: Styles = inner.parse()?; // MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site())) // } else { // just parse as an expression MaybeExpr::Expr(outer.parse()?) // } } else { s.parse()? }; AttrType::Value(lit_str) }; Ok(Attr { name, ty }) } } impl ToTokens for ToToksCtx<&Attr> { fn to_tokens(&self, tokens: &mut TokenStream2) { let name = self.inner.name.to_string(); let _attr_stream = TokenStream2::new(); match &self.inner.ty { AttrType::Value(value) => { let value = self.recurse(value); if name == "xmlns" { tokens.append_all(quote! { .namespace(Some(#value)) }); } else { tokens.append_all(quote! { .attr(#name, format_args_f!(#value)) }); } } AttrType::Event(event) => { tokens.append_all(quote! { .on(#name, #event) }); } } } } enum AttrType { Value(MaybeExpr), Event(ExprClosure), // todo Bool(MaybeExpr) } /// ======================================= /// Parse just plain text /// ======================================= /// - [ ] Perform formatting automatically /// /// struct TextNode(MaybeExpr); impl Parse for TextNode { fn parse(s: ParseStream) -> Result { Ok(Self(s.parse()?)) } } impl ToTokens for ToToksCtx<&TextNode> { fn to_tokens(&self, tokens: &mut TokenStream2) { let mut token_stream = TokenStream2::new(); self.recurse(&self.inner.0).to_tokens(&mut token_stream); tokens.append_all(quote! { __cx.text(format_args_f!(#token_stream)) }); } } #[allow(clippy::large_enum_variant)] enum MaybeExpr { Literal(T), Expr(Expr), } impl Parse for MaybeExpr { fn parse(s: ParseStream) -> Result { if s.peek(token::Brace) { let content; syn::braced!(content in s); Ok(MaybeExpr::Expr(content.parse()?)) } else { Ok(MaybeExpr::Literal(s.parse()?)) } } } impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr> where T: 'a, ToToksCtx<&'a T>: ToTokens, { fn to_tokens(&self, tokens: &mut TokenStream2) { match &self.inner { MaybeExpr::Literal(v) => self.recurse(v).to_tokens(tokens), MaybeExpr::Expr(expr) => expr.to_tokens(tokens), } } } /// ToTokens context struct ToToksCtx { inner: T, } impl<'a, T> ToToksCtx { fn new(inner: T) -> Self { ToToksCtx { inner } } fn recurse(&self, inner: U) -> ToToksCtx { ToToksCtx { inner } } } impl ToTokens for ToToksCtx<&LitStr> { fn to_tokens(&self, tokens: &mut TokenStream2) { self.inner.to_tokens(tokens) } } #[cfg(test)] mod test { fn parse(input: &str) -> super::Result { syn::parse_str(input) } #[test] fn div() { parse("bump,
").unwrap(); } #[test] fn nested() { parse("bump,
\"text\"
").unwrap(); } #[test] fn complex() { parse( "bump,
{contact_details}
", ) .unwrap(); } }