use crate::innerlude::*;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens;
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
spanned::Spanned,
token::{self},
Ident, LitStr, Result, Token,
};
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum BodyNode {
Element(Element),
Component(Component),
Text(TextNode),
RawExpr(ExprNode),
ForLoop(ForLoop),
IfChain(IfChain),
}
impl Parse for BodyNode {
fn parse(stream: ParseStream) -> Result<Self> {
if stream.peek(LitStr) {
return Ok(BodyNode::Text(stream.parse()?));
}
if stream.peek(Token![for]) {
return Ok(BodyNode::ForLoop(stream.parse()?));
}
if stream.peek(Token![if]) {
return Ok(BodyNode::IfChain(stream.parse()?));
}
if stream.peek(Token![match]) {
return Ok(BodyNode::RawExpr(stream.parse()?));
}
if stream.peek(token::Brace) {
return Ok(BodyNode::RawExpr(stream.parse()?));
}
if stream.peek(Ident::peek_any) && stream.peek2(Token![-]) {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
if stream.peek(Ident::peek_any) && !stream.peek2(Token![::]) {
let ident = parse_raw_ident(&stream.fork()).unwrap();
let el_name = ident.to_string();
let first_char = el_name.chars().next().unwrap();
if first_char.is_ascii_lowercase() && !el_name.contains('_') {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
Ok(BodyNode::Component(stream.parse()?))
}
}
impl ToTokens for BodyNode {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
BodyNode::Element(ela) => ela.to_tokens(tokens),
BodyNode::RawExpr(exp) => exp.to_tokens(tokens),
BodyNode::Text(txt) => txt.to_tokens(tokens),
BodyNode::ForLoop(floop) => floop.to_tokens(tokens),
BodyNode::Component(comp) => comp.to_tokens(tokens),
BodyNode::IfChain(ifchain) => ifchain.to_tokens(tokens),
}
}
}
impl BodyNode {
pub fn get_dyn_idx(&self) -> usize {
match self {
BodyNode::Text(text) => text.dyn_idx.get(),
BodyNode::RawExpr(exp) => exp.dyn_idx.get(),
BodyNode::Component(comp) => comp.dyn_idx.get(),
BodyNode::ForLoop(floop) => floop.dyn_idx.get(),
BodyNode::IfChain(chain) => chain.dyn_idx.get(),
BodyNode::Element(_) => panic!("Cannot get dyn_idx for this node"),
}
}
pub fn set_dyn_idx(&self, idx: usize) {
match self {
BodyNode::Text(text) => text.dyn_idx.set(idx),
BodyNode::RawExpr(exp) => exp.dyn_idx.set(idx),
BodyNode::Component(comp) => comp.dyn_idx.set(idx),
BodyNode::ForLoop(floop) => floop.dyn_idx.set(idx),
BodyNode::IfChain(chain) => chain.dyn_idx.set(idx),
BodyNode::Element(_) => panic!("Cannot set dyn_idx for this node"),
}
}
pub fn is_litstr(&self) -> bool {
matches!(self, BodyNode::Text { .. })
}
pub fn span(&self) -> Span {
match self {
BodyNode::Element(el) => el.name.span(),
BodyNode::Component(component) => component.name.span(),
BodyNode::Text(text) => text.input.span(),
BodyNode::RawExpr(exp) => exp.span(),
BodyNode::ForLoop(fl) => fl.for_token.span(),
BodyNode::IfChain(f) => f.if_token.span(),
}
}
pub fn element_children(&self) -> &[BodyNode] {
match self {
BodyNode::Element(el) => &el.children,
_ => panic!("Children not available for this node"),
}
}
pub fn el_name(&self) -> &ElementName {
match self {
BodyNode::Element(el) => &el.name,
_ => panic!("Element name not available for this node"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn parsing_matches() {
let element = quote! { div { class: "inline-block mr-4", icons::icon_14 {} } };
assert!(matches!(
syn::parse2::<BodyNode>(element).unwrap(),
BodyNode::Element(_)
));
let text = quote! { "Hello, world!" };
assert!(matches!(
syn::parse2::<BodyNode>(text).unwrap(),
BodyNode::Text(_)
));
let component = quote! { Component {} };
assert!(matches!(
syn::parse2::<BodyNode>(component).unwrap(),
BodyNode::Component(_)
));
let raw_expr = quote! { { 1 + 1 } };
assert!(matches!(
syn::parse2::<BodyNode>(raw_expr).unwrap(),
BodyNode::RawExpr(_)
));
let for_loop = quote! { for item in items {} };
assert!(matches!(
syn::parse2::<BodyNode>(for_loop).unwrap(),
BodyNode::ForLoop(_)
));
let if_chain = quote! { if cond {} else if cond {} };
assert!(matches!(
syn::parse2::<BodyNode>(if_chain).unwrap(),
BodyNode::IfChain(_)
));
let match_expr = quote! {
match blah {
val => rsx! { div {} },
other_val => rsx! { div {} }
}
};
assert!(matches!(
syn::parse2::<BodyNode>(match_expr).unwrap(),
BodyNode::RawExpr(_)
),);
let incomplete_component = quote! {
some::cool::Component
};
assert!(matches!(
syn::parse2::<BodyNode>(incomplete_component).unwrap(),
BodyNode::Component(_)
),);
}
}