//! The actual rsx! macro implementation. //! //! This mostly just defers to the root TemplateBody with some additional tooling to provide better errors. //! Currently the additional tooling doesn't do much. use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use std::{cell::Cell, fmt::Debug}; use syn::{ parse::{Parse, ParseStream}, Result, }; use crate::{BodyNode, TemplateBody}; /// The Callbody is the contents of the rsx! macro /// /// It is a list of BodyNodes, which are the different parts of the template. /// The Callbody contains no information about how the template will be rendered, only information about the parsed tokens. /// /// Every callbody should be valid, so you can use it to build a template. /// To generate the code used to render the template, use the ToTokens impl on the Callbody, or with the `render_with_location` method. /// /// Ideally we don't need the metadata here and can bake the idx-es into the templates themselves but I haven't figured out how to do that yet. #[derive(Debug, Clone)] pub struct CallBody { pub body: TemplateBody, pub template_idx: Cell, } impl Parse for CallBody { fn parse(input: ParseStream) -> Result { // Defer to the `new` method such that we can wire up hotreload information Ok(CallBody::new(input.parse()?)) } } impl ToTokens for CallBody { fn to_tokens(&self, out: &mut TokenStream2) { self.body.to_tokens(out) } } impl CallBody { /// Create a new CallBody from a TemplateBody /// /// This will overwrite all internal metadata regarding hotreloading. pub fn new(body: TemplateBody) -> Self { let body = CallBody { body, template_idx: Cell::new(0), }; body.body.template_idx.set(body.next_template_idx()); body.cascade_hotreload_info(&body.body.roots); body } /// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro /// /// This should be preferred over `parse` if you are outside of a macro pub fn parse_strict(input: ParseStream) -> Result { // todo: actually throw warnings if there are any Self::parse(input) } /// With the entire knowledge of the macro call, wire up location information for anything hotreloading /// specific. It's a little bit simpler just to have a global id per callbody than to try and track it /// relative to each template, though we could do that if we wanted to. /// /// For now this is just information for ifmts and templates so that when they generate, they can be /// tracked back to the original location in the source code, to support formatted string hotreloading. /// /// Note that there are some more complex cases we could in theory support, but have bigger plans /// to enable just pure rust hotreloading that would make those tricks moot. So, manage more of /// the simple cases until that proper stuff ships. /// /// We need to make sure to wire up: /// - subtemplate IDs /// - ifmt IDs /// - dynamic node IDs /// - dynamic attribute IDs /// - paths for dynamic nodes and attributes /// /// Lots of wiring! /// /// However, here, we only need to wire up template IDs since TemplateBody will handle the rest. /// /// This is better though since we can save the relevant data on the structures themselves. fn cascade_hotreload_info(&self, nodes: &[BodyNode]) { for node in nodes.iter() { match node { BodyNode::Element(el) => { self.cascade_hotreload_info(&el.children); } BodyNode::Component(comp) => { comp.children.template_idx.set(self.next_template_idx()); self.cascade_hotreload_info(&comp.children.roots); } BodyNode::ForLoop(floop) => { floop.body.template_idx.set(self.next_template_idx()); self.cascade_hotreload_info(&floop.body.roots); } BodyNode::IfChain(chain) => chain.for_each_branch(&mut |body| { body.template_idx.set(self.next_template_idx()); self.cascade_hotreload_info(&body.roots) }), _ => {} } } } fn next_template_idx(&self) -> usize { let idx = self.template_idx.get(); self.template_idx.set(idx + 1); idx } }