123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- //! Parse the root tokens in the rsx!{} macro
- //! =========================================
- //!
- //! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.
- //! This feature must support:
- //! - [x] Optionally rendering if the `in XYZ` pattern is present
- //! - [x] Fragments as top-level element (through ambiguous)
- //! - [x] Components as top-level element (through ambiguous)
- //! - [x] Tags as top-level elements (through ambiguous)
- //! - [x] Good errors if parsing fails
- //!
- //! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
- #[macro_use]
- mod errors;
- mod component;
- mod element;
- mod ifmt;
- mod node;
- mod template;
- // Re-export the namespaces into each other
- pub use component::*;
- pub use element::*;
- pub use ifmt::*;
- pub use node::*;
- // imports
- use proc_macro2::TokenStream as TokenStream2;
- use quote::{quote, ToTokens, TokenStreamExt};
- use syn::{
- parse::{Parse, ParseStream},
- Result, Token,
- };
- /// Fundametnally, every CallBody is a template
- #[derive(Default, Debug)]
- pub struct CallBody {
- pub roots: Vec<BodyNode>,
- // set this after
- pub inline_cx: bool,
- }
- impl Parse for CallBody {
- fn parse(input: ParseStream) -> Result<Self> {
- let mut roots = Vec::new();
- while !input.is_empty() {
- let node = input.parse::<BodyNode>()?;
- if input.peek(Token![,]) {
- let _ = input.parse::<Token![,]>();
- }
- roots.push(node);
- }
- Ok(Self {
- roots,
- inline_cx: false,
- })
- }
- }
- /// Serialize the same way, regardless of flavor
- impl ToTokens for CallBody {
- fn to_tokens(&self, out_tokens: &mut TokenStream2) {
- let body = TemplateRenderer { roots: &self.roots };
- if self.inline_cx {
- out_tokens.append_all(quote! {
- Ok({
- let __cx = cx;
- #body
- })
- })
- } else {
- out_tokens.append_all(quote! {
- ::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
- #body
- })
- })
- }
- }
- }
- pub struct TemplateRenderer<'a> {
- pub roots: &'a [BodyNode],
- }
- impl<'a> ToTokens for TemplateRenderer<'a> {
- fn to_tokens(&self, out_tokens: &mut TokenStream2) {
- let mut context = DynamicContext {
- dynamic_nodes: vec![],
- dynamic_attributes: vec![],
- current_path: vec![],
- attr_paths: vec![],
- node_paths: vec![],
- };
- let key = match self.roots.get(0) {
- Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
- Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(),
- _ => None,
- };
- let key_tokens = match key {
- Some(tok) => quote! { Some( __cx.raw_text(#tok) ) },
- None => quote! { None },
- };
- let spndbg = format!("{:?}", self.roots[0].span());
- let root_col = spndbg[9..].split("..").next().unwrap();
- let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
- context.current_path.push(idx as u8);
- let out = context.render_static_node(root);
- context.current_path.pop();
- out
- });
- // Render and release the mutable borrow on context
- let num_roots = self.roots.len();
- let roots = quote! { #( #root_printer ),* };
- let node_printer = &context.dynamic_nodes;
- let dyn_attr_printer = &context.dynamic_attributes;
- let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
- let attr_paths = context.attr_paths.iter().map(|it| quote!(&[#(#it),*]));
- out_tokens.append_all(quote! {
- static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
- name: concat!(
- file!(),
- ":",
- line!(),
- ":",
- column!(),
- ":",
- #root_col
- ),
- roots: &[ #roots ],
- node_paths: &[ #(#node_paths),* ],
- attr_paths: &[ #(#attr_paths),* ],
- };
- ::dioxus::core::VNode {
- parent: None,
- key: #key_tokens,
- template: TEMPLATE,
- root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut _).as_slice_of_cells(),
- // root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut [::dioxus::core::ElementId]).as_slice_of_cells(),
- dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
- dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
- }
- });
- }
- }
- // As we print out the dynamic nodes, we want to keep track of them in a linear fashion
- // We'll use the size of the vecs to determine the index of the dynamic node in the final
- pub struct DynamicContext<'a> {
- dynamic_nodes: Vec<&'a BodyNode>,
- dynamic_attributes: Vec<&'a ElementAttrNamed>,
- current_path: Vec<u8>,
- node_paths: Vec<Vec<u8>>,
- attr_paths: Vec<Vec<u8>>,
- }
- impl<'a> DynamicContext<'a> {
- fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
- match root {
- BodyNode::Element(el) => {
- let el_name = &el.name;
- // dynamic attributes
- // [0]
- // [0, 1]
- // [0, 1]
- // [0, 1]
- // [0, 1, 2]
- // [0, 2]
- // [0, 2, 1]
- let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
- ElementAttr::AttrText { name, value } if value.is_static() => {
- let value = value.to_static().unwrap();
- quote! {
- ::dioxus::core::TemplateAttribute::Static {
- name: dioxus_elements::#el_name::#name.0,
- namespace: dioxus_elements::#el_name::#name.1,
- value: #value,
- // todo: we don't diff these so we never apply the volatile flag
- // volatile: dioxus_elements::#el_name::#name.2,
- }
- }
- }
- ElementAttr::CustomAttrText { name, value } if value.is_static() => {
- let value = value.to_static().unwrap();
- quote! {
- ::dioxus::core::TemplateAttribute::Static {
- name: #name,
- namespace: None,
- value: #value,
- // todo: we don't diff these so we never apply the volatile flag
- // volatile: dioxus_elements::#el_name::#name.2,
- }
- }
- }
- ElementAttr::AttrExpression { .. }
- | ElementAttr::AttrText { .. }
- | ElementAttr::CustomAttrText { .. }
- | ElementAttr::CustomAttrExpression { .. }
- | ElementAttr::EventTokens { .. } => {
- let ct = self.dynamic_attributes.len();
- self.dynamic_attributes.push(attr);
- self.attr_paths.push(self.current_path.clone());
- quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
- }
- });
- let attrs = quote! { #(#static_attrs),*};
- let children = el.children.iter().enumerate().map(|(idx, root)| {
- self.current_path.push(idx as u8);
- let out = self.render_static_node(root);
- self.current_path.pop();
- out
- });
- let _opt = el.children.len() == 1;
- let children = quote! { #(#children),* };
- quote! {
- ::dioxus::core::TemplateNode::Element {
- tag: dioxus_elements::#el_name::TAG_NAME,
- namespace: dioxus_elements::#el_name::NAME_SPACE,
- attrs: &[ #attrs ],
- children: &[ #children ],
- }
- }
- }
- BodyNode::Text(text) if text.is_static() => {
- let text = text.to_static().unwrap();
- quote! { ::dioxus::core::TemplateNode::Text{ text: #text } }
- }
- BodyNode::RawExpr(_)
- | BodyNode::Text(_)
- | BodyNode::ForLoop(_)
- | BodyNode::IfChain(_)
- | BodyNode::Component(_) => {
- let ct = self.dynamic_nodes.len();
- self.dynamic_nodes.push(root);
- self.node_paths.push(self.current_path.clone());
- match root {
- BodyNode::Text(_) => {
- quote! { ::dioxus::core::TemplateNode::DynamicText { id: #ct } }
- }
- _ => quote! { ::dioxus::core::TemplateNode::Dynamic { id: #ct } },
- }
- }
- }
- }
- }
|