use super::{NodeProps, NodeSubTree};
use crate::optional_parse::{impl_parse_for_optional_parse, OptionalParse};
use crate::scope::Scope;
use quote::ToTokens;
use quote::{quote, TokenStreamExt};
use syn::Token;
use syn::__private::TokenStream2;
use syn::parse::ParseStream;
type Arg = syn::Expr;
pub enum NodeType {
FunctionalComponent {
args: Vec<Arg>,
path: TokenStream2,
constructor: TokenStream2,
},
Element { name: String, props: NodeProps },
Component {
args: Vec<Arg>,
props: NodeProps,
path: TokenStream2,
constructor: TokenStream2,
},
}
impl NodeType {
pub fn get_init_call(&self) -> TokenStream2 {
match self {
NodeType::FunctionalComponent {
args,
path,
constructor,
}
| NodeType::Component {
args,
path,
constructor,
..
} => {
let mut path = path.to_token_stream();
if !path.is_empty() {
path.append_all(quote! {::})
};
quote! { #path#constructor(#(#args),*) }
}
NodeType::Element { name, .. } => quote! { gxi::Element::from(#name) },
}
}
pub fn get_return_type(&self) -> TokenStream2 {
match self {
NodeType::FunctionalComponent { .. } => {
unreachable!("Internal Error: functional components have unknown return type")
}
NodeType::Element { .. } => quote! {gxi::Element},
NodeType::Component { path, .. } => path.to_token_stream(),
}
}
pub fn get_const_and_observable_props(
&self,
return_type: &TokenStream2,
) -> (TokenStream2, TokenStream2) {
let mut const_props = TokenStream2::new();
let mut observable_props = TokenStream2::new();
let props = self.get_props();
for prop in &props.props {
if let Scope::Constant = prop.scope {
prop.to_tokens(&mut const_props, return_type);
} else {
prop.to_tokens(&mut observable_props, return_type);
}
}
(const_props, observable_props)
}
pub fn get_props(&self) -> &NodeProps {
match self {
NodeType::FunctionalComponent { .. } => {
unreachable!("Internal Error: functional component's can't have props.")
}
NodeType::Element { props, .. } | NodeType::Component { props, .. } => props,
}
}
}
fn starts_with_lower_case(string: &str) -> bool {
matches!(string.chars().next().unwrap(), 'a'..='z')
}
impl NodeType {
fn parse(input: ParseStream) -> syn::Result<Option<Self>> {
if input.is_empty() {
return Err(syn::Error::new(input.span(), "expected tokens"));
}
#[allow(clippy::question_mark)]
let mut path = if let Ok(path) = input.parse::<syn::Path>() {
path
} else {
return Ok(None);
};
let last_segment = path.segments.last().unwrap();
let last_segment = last_segment.ident.to_string();
return Ok(Some(
if !starts_with_lower_case(&last_segment) {
Self::Component {
args: Default::default(),
props: input.parse()?,
path: path.to_token_stream(),
constructor: quote! {new},
}
} else {
if path.segments.len() == 1 {
let mut props = NodeProps::default();
if let Ok(syn::group::Parens { content, .. }) = syn::group::parse_parens(input)
{
if content.peek(Token!(const)) || content.peek2(Token!(=)) {
while !content.is_empty() {
props.props.push(content.parse()?);
if !content.is_empty() {
content.parse::<syn::token::Comma>()?;
}
}
} else {
let mut args = Vec::<Arg>::default();
while !content.is_empty() {
args.push(content.parse()?);
if !content.is_empty() {
content.parse::<syn::token::Comma>()?;
}
}
return Ok(Some(Self::FunctionalComponent {
args,
path: TokenStream2::new(),
constructor: path.to_token_stream(),
}));
}
}
Self::Element {
name: last_segment,
props,
}
} else {
let mut args = Vec::<Arg>::default();
if let Ok(syn::group::Parens { content, .. }) = syn::group::parse_parens(input)
{
while !content.is_empty() {
args.push(content.parse()?);
if !content.is_empty() {
content.parse::<syn::Token!(,)>()?;
}
}
}
let constructor = path.segments.pop().unwrap().into_value().to_token_stream();
let second_last_starts_with_upper_case =
!starts_with_lower_case(&path.segments.last().unwrap().ident.to_string());
let mut path = path.to_token_stream().to_string();
if path.ends_with("::") {
path.pop();
path.pop();
}
let path = syn::parse_str(&path)?;
if second_last_starts_with_upper_case {
Self::Component {
args,
props: input.parse()?,
path,
constructor,
}
} else {
Self::FunctionalComponent {
args,
path,
constructor,
}
}
}
},
));
}
}
#[doc = include_str ! ("./README.md")]
pub struct NodeBlock {
pub node_type: NodeType,
pub subtree: NodeSubTree,
}
impl OptionalParse for NodeBlock {
fn optional_parse(input: &ParseStream) -> syn::Result<Option<Self>> {
let node_type = if let Some(node_type) = NodeType::parse(input)? {
node_type
} else {
return Ok(None);
};
let subtree =
if let Ok(syn::group::Brackets { content, .. }) = syn::group::parse_brackets(input) {
if !content.is_empty() {
content.parse::<NodeSubTree>()?
} else {
Default::default()
}
} else {
Default::default()
};
Ok(Some(Self { node_type, subtree }))
}
}
impl_parse_for_optional_parse!(NodeBlock);
impl ToTokens for NodeBlock {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self { subtree, node_type } = self;
let init_call = node_type.get_init_call();
let mid_calls = match node_type {
NodeType::FunctionalComponent { .. } => TokenStream2::new(),
_ => {
let return_type = node_type.get_return_type();
let (const_props, observable_props) =
node_type.get_const_and_observable_props(&return_type);
let mut subtree_tokens = TokenStream2::new();
subtree.to_tokens(&mut subtree_tokens, &return_type);
quote! {
#const_props
#subtree_tokens
#observable_props
}
}
};
tokens.append_all(quote! {{
use gxi::{VNode, VContainerWidget};
let mut __node = #init_call;
#mid_calls
__node
}});
}
}
impl ToString for NodeBlock {
fn to_string(&self) -> String {
if cfg!(feature = "web") {
} else {
unreachable!("Can't serialize with the current feature flag. Most likely an internal error. Please use the github issue tracker https://github.com/gxi-rs/gxi/issues")
}
todo!()
}
}
#[cfg(test)]
mod tests {
use super::NodeType;
use anyhow::{bail, ensure};
use quote::quote;
use syn::parse::Parse;
struct NodeTypeParser(NodeType);
impl Parse for NodeTypeParser {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self(NodeType::parse(&input)?.unwrap()))
}
}
#[test]
fn node_type() -> anyhow::Result<()> {
{
let node_type = syn::parse2::<NodeTypeParser>(quote! { Body }.into())?.0;
if let NodeType::Component {
constructor,
path,
args,
props,
} = &node_type
{
ensure!(constructor.to_string() == "new");
ensure!(path.to_string() == "Body");
ensure!(args.len() == 0);
ensure!(props.props.len() == 0);
} else {
bail!("wrong node type")
}
let return_type = node_type.get_return_type();
ensure!(return_type.to_string() == "Body");
ensure!(node_type.get_init_call().to_string() == quote! {Body::new()}.to_string());
}
{
let node_type = syn::parse2::<NodeTypeParser>(quote! { func(12, 12) }.into())?.0;
if let NodeType::FunctionalComponent {
constructor,
path,
args,
} = &node_type
{
ensure!(constructor.to_string() == "func");
ensure!(path.is_empty() == true);
ensure!(args.len() == 2);
} else {
bail!("wrong node type")
}
ensure!(node_type.get_init_call().to_string() == quote! {func(12, 12)}.to_string());
}
{
let node_type =
syn::parse2::<NodeTypeParser>(quote! { Comp::with_name("hey") }.into())?.0;
if let NodeType::Component {
args,
props,
path,
constructor,
} = &node_type
{
ensure!(constructor.to_string() == "with_name");
ensure!(path.to_string() == quote! {Comp}.to_string());
ensure!(props.props.is_empty() == true);
ensure!(args.len() == 1);
} else {
bail!("wrong node type")
}
let return_type = node_type.get_return_type();
ensure!(return_type.to_string() == quote! {Comp}.to_string());
ensure!(
node_type.get_init_call().to_string()
== quote! {Comp::with_name("hey")}.to_string()
);
}
Ok(())
}
}