1use crate::innerlude::*;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::ToTokens;
4use syn::{
5 ext::IdentExt,
6 parse::{Parse, ParseStream},
7 spanned::Spanned,
8 token::{self},
9 Ident, LitStr, Result, Token,
10};
11
12#[derive(PartialEq, Eq, Clone, Debug)]
13pub enum BodyNode {
14 Element(Element),
16
17 Component(Component),
19
20 Text(TextNode),
22
23 RawExpr(ExprNode),
25
26 ForLoop(ForLoop),
28
29 IfChain(IfChain),
31}
32
33impl Parse for BodyNode {
34 fn parse(stream: ParseStream) -> Result<Self> {
35 if stream.peek(LitStr) {
36 return Ok(BodyNode::Text(stream.parse()?));
37 }
38
39 if stream.peek(Token![for]) {
41 return Ok(BodyNode::ForLoop(stream.parse()?));
42 }
43
44 if stream.peek(Token![if]) {
46 return Ok(BodyNode::IfChain(stream.parse()?));
47 }
48
49 if stream.peek(Token![match]) {
63 return Ok(BodyNode::RawExpr(stream.parse()?));
64 }
65
66 if stream.peek(token::Brace) {
68 return Ok(BodyNode::RawExpr(stream.parse()?));
69 }
70
71 if stream.peek(Ident::peek_any) && stream.peek2(Token![-]) {
74 return Ok(BodyNode::Element(stream.parse::<Element>()?));
75 }
76
77 if stream.peek(Ident::peek_any) && !stream.peek2(Token![::]) {
88 let ident = parse_raw_ident(&stream.fork()).unwrap();
89 let el_name = ident.to_string();
90 let first_char = el_name.chars().next().unwrap();
91
92 if first_char.is_ascii_lowercase() && !el_name.contains('_') {
93 return Ok(BodyNode::Element(stream.parse::<Element>()?));
94 }
95 }
96
97 Ok(BodyNode::Component(stream.parse()?))
112 }
113}
114
115impl ToTokens for BodyNode {
116 fn to_tokens(&self, tokens: &mut TokenStream2) {
117 match self {
118 BodyNode::Element(ela) => ela.to_tokens(tokens),
119 BodyNode::RawExpr(exp) => exp.to_tokens(tokens),
120 BodyNode::Text(txt) => txt.to_tokens(tokens),
121 BodyNode::ForLoop(floop) => floop.to_tokens(tokens),
122 BodyNode::Component(comp) => comp.to_tokens(tokens),
123 BodyNode::IfChain(ifchain) => ifchain.to_tokens(tokens),
124 }
125 }
126}
127
128impl BodyNode {
129 pub fn get_dyn_idx(&self) -> usize {
130 match self {
131 BodyNode::Text(text) => text.dyn_idx.get(),
132 BodyNode::RawExpr(exp) => exp.dyn_idx.get(),
133 BodyNode::Component(comp) => comp.dyn_idx.get(),
134 BodyNode::ForLoop(floop) => floop.dyn_idx.get(),
135 BodyNode::IfChain(chain) => chain.dyn_idx.get(),
136 BodyNode::Element(_) => panic!("Cannot get dyn_idx for this node"),
137 }
138 }
139
140 pub fn set_dyn_idx(&self, idx: usize) {
141 match self {
142 BodyNode::Text(text) => text.dyn_idx.set(idx),
143 BodyNode::RawExpr(exp) => exp.dyn_idx.set(idx),
144 BodyNode::Component(comp) => comp.dyn_idx.set(idx),
145 BodyNode::ForLoop(floop) => floop.dyn_idx.set(idx),
146 BodyNode::IfChain(chain) => chain.dyn_idx.set(idx),
147 BodyNode::Element(_) => panic!("Cannot set dyn_idx for this node"),
148 }
149 }
150
151 pub fn is_litstr(&self) -> bool {
152 matches!(self, BodyNode::Text { .. })
153 }
154
155 pub fn span(&self) -> Span {
156 match self {
157 BodyNode::Element(el) => el.name.span(),
158 BodyNode::Component(component) => component.name.span(),
159 BodyNode::Text(text) => text.input.span(),
160 BodyNode::RawExpr(exp) => exp.span(),
161 BodyNode::ForLoop(fl) => fl.for_token.span(),
162 BodyNode::IfChain(f) => f.if_token.span(),
163 }
164 }
165
166 pub fn element_children(&self) -> &[BodyNode] {
167 match self {
168 BodyNode::Element(el) => &el.children,
169 _ => panic!("Children not available for this node"),
170 }
171 }
172
173 pub fn el_name(&self) -> &ElementName {
174 match self {
175 BodyNode::Element(el) => &el.name,
176 _ => panic!("Element name not available for this node"),
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use quote::quote;
185
186 #[test]
187 fn parsing_matches() {
188 let element = quote! { div { class: "inline-block mr-4", icons::icon_14 {} } };
189 assert!(matches!(
190 syn::parse2::<BodyNode>(element).unwrap(),
191 BodyNode::Element(_)
192 ));
193
194 let text = quote! { "Hello, world!" };
195 assert!(matches!(
196 syn::parse2::<BodyNode>(text).unwrap(),
197 BodyNode::Text(_)
198 ));
199
200 let component = quote! { Component {} };
201 assert!(matches!(
202 syn::parse2::<BodyNode>(component).unwrap(),
203 BodyNode::Component(_)
204 ));
205
206 let raw_expr = quote! { { 1 + 1 } };
207 assert!(matches!(
208 syn::parse2::<BodyNode>(raw_expr).unwrap(),
209 BodyNode::RawExpr(_)
210 ));
211
212 let for_loop = quote! { for item in items {} };
213 assert!(matches!(
214 syn::parse2::<BodyNode>(for_loop).unwrap(),
215 BodyNode::ForLoop(_)
216 ));
217
218 let if_chain = quote! { if cond {} else if cond {} };
219 assert!(matches!(
220 syn::parse2::<BodyNode>(if_chain).unwrap(),
221 BodyNode::IfChain(_)
222 ));
223
224 let match_expr = quote! {
225 match blah {
226 val => rsx! { div {} },
227 other_val => rsx! { div {} }
228 }
229 };
230 assert!(matches!(
231 syn::parse2::<BodyNode>(match_expr).unwrap(),
232 BodyNode::RawExpr(_)
233 ),);
234
235 let incomplete_component = quote! {
236 some::cool::Component
237 };
238 assert!(matches!(
239 syn::parse2::<BodyNode>(incomplete_component).unwrap(),
240 BodyNode::Component(_)
241 ),);
242 }
243}