dioxus_rsx/rsx_call.rs
1//! The actual rsx! macro implementation.
2//!
3//! This mostly just defers to the root TemplateBody with some additional tooling to provide better errors.
4//! Currently the additional tooling doesn't do much.
5
6use proc_macro2::TokenStream as TokenStream2;
7use quote::ToTokens;
8use std::{cell::Cell, fmt::Debug};
9use syn::{
10 parse::{Parse, ParseStream},
11 Result,
12};
13
14use crate::{BodyNode, TemplateBody};
15
16/// The Callbody is the contents of the rsx! macro
17///
18/// It is a list of BodyNodes, which are the different parts of the template.
19/// The Callbody contains no information about how the template will be rendered, only information about the parsed tokens.
20///
21/// Every callbody should be valid, so you can use it to build a template.
22/// To generate the code used to render the template, use the ToTokens impl on the Callbody, or with the `render_with_location` method.
23///
24/// 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.
25#[derive(Debug, Clone)]
26pub struct CallBody {
27 pub body: TemplateBody,
28 pub template_idx: Cell<usize>,
29}
30
31impl Parse for CallBody {
32 fn parse(input: ParseStream) -> Result<Self> {
33 // Defer to the `new` method such that we can wire up hotreload information
34 Ok(CallBody::new(input.parse()?))
35 }
36}
37
38impl ToTokens for CallBody {
39 fn to_tokens(&self, out: &mut TokenStream2) {
40 self.body.to_tokens(out)
41 }
42}
43
44impl CallBody {
45 /// Create a new CallBody from a TemplateBody
46 ///
47 /// This will overwrite all internal metadata regarding hotreloading.
48 pub fn new(body: TemplateBody) -> Self {
49 let body = CallBody {
50 body,
51 template_idx: Cell::new(0),
52 };
53
54 body.body.template_idx.set(body.next_template_idx());
55
56 body.cascade_hotreload_info(&body.body.roots);
57
58 body
59 }
60
61 /// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro
62 ///
63 /// This should be preferred over `parse` if you are outside of a macro
64 pub fn parse_strict(input: ParseStream) -> Result<Self> {
65 // todo: actually throw warnings if there are any
66 Self::parse(input)
67 }
68
69 /// With the entire knowledge of the macro call, wire up location information for anything hotreloading
70 /// specific. It's a little bit simpler just to have a global id per callbody than to try and track it
71 /// relative to each template, though we could do that if we wanted to.
72 ///
73 /// For now this is just information for ifmts and templates so that when they generate, they can be
74 /// tracked back to the original location in the source code, to support formatted string hotreloading.
75 ///
76 /// Note that there are some more complex cases we could in theory support, but have bigger plans
77 /// to enable just pure rust hotreloading that would make those tricks moot. So, manage more of
78 /// the simple cases until that proper stuff ships.
79 ///
80 /// We need to make sure to wire up:
81 /// - subtemplate IDs
82 /// - ifmt IDs
83 /// - dynamic node IDs
84 /// - dynamic attribute IDs
85 /// - paths for dynamic nodes and attributes
86 ///
87 /// Lots of wiring!
88 ///
89 /// However, here, we only need to wire up template IDs since TemplateBody will handle the rest.
90 ///
91 /// This is better though since we can save the relevant data on the structures themselves.
92 fn cascade_hotreload_info(&self, nodes: &[BodyNode]) {
93 for node in nodes.iter() {
94 match node {
95 BodyNode::Element(el) => {
96 self.cascade_hotreload_info(&el.children);
97 }
98
99 BodyNode::Component(comp) => {
100 comp.children.template_idx.set(self.next_template_idx());
101 self.cascade_hotreload_info(&comp.children.roots);
102 }
103
104 BodyNode::ForLoop(floop) => {
105 floop.body.template_idx.set(self.next_template_idx());
106 self.cascade_hotreload_info(&floop.body.roots);
107 }
108
109 BodyNode::IfChain(chain) => chain.for_each_branch(&mut |body| {
110 body.template_idx.set(self.next_template_idx());
111 self.cascade_hotreload_info(&body.roots)
112 }),
113
114 _ => {}
115 }
116 }
117 }
118
119 fn next_template_idx(&self) -> usize {
120 let idx = self.template_idx.get();
121 self.template_idx.set(idx + 1);
122 idx
123 }
124}