wip: serious refactor with const generics

This commit is contained in:
Jonathan Kelley 2021-07-21 22:55:04 -04:00
parent 308414f5ef
commit 160d86abbe
16 changed files with 492 additions and 286 deletions

View file

@ -1,27 +0,0 @@
//! Html body
//! -------
//!
//!
//! Since both HTML and RSX serialize to the same node structure, the HTML parser uses the same types as RSX,
//! but has a different Parse implementation.
use crate::rsx::*;
use quote::ToTokens;
use syn::parse::Parse;
pub struct HtmlBody(RsxBody);
impl Parse for HtmlBody {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
todo!()
}
}
impl ToTokens for HtmlBody {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.0.to_tokens(tokens)
}
}
pub struct HtmlNode(BodyNode);
pub struct HtmlAmbigiousElement(AmbiguousElement);
pub struct HtmlComponent(Component);

View file

@ -1,69 +1,14 @@
use proc_macro::TokenStream;
use quote::ToTokens;
use rsx::{AS_HTML, AS_RSX};
use syn::parse_macro_input;
pub(crate) mod fc;
pub(crate) mod htm;
pub(crate) mod html;
pub(crate) mod ifmt;
pub(crate) mod props;
pub(crate) mod rsx;
pub(crate) mod rsxtemplate;
pub(crate) mod util;
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
/// We aim to keep functional parity with html templates.
#[proc_macro]
pub fn html(s: TokenStream) -> TokenStream {
match syn::parse::<htm::HtmlRender>(s) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}
}
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
/// We aim to keep functional parity with html templates.
#[proc_macro]
pub fn rsx_template(s: TokenStream) -> TokenStream {
match syn::parse::<rsxtemplate::RsxTemplate>(s) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}
}
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
/// We aim to keep functional parity with html templates.
#[proc_macro]
pub fn html_template(s: TokenStream) -> TokenStream {
match syn::parse::<rsxtemplate::RsxTemplate>(s) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}
}
// #[proc_macro_attribute]
// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
/// Label a function or static closure as a functional component.
/// This macro reduces the need to create a separate properties struct.
///
/// Using this macro is fun and simple
///
/// ```ignore
///
/// #[fc]
/// fn Example(cx: Context, name: &str) -> DomTree {
/// cx.render(rsx! { h1 {"hello {name}"} })
/// }
/// ```
#[proc_macro_attribute]
pub fn fc(_attr: TokenStream, item: TokenStream) -> TokenStream {
match syn::parse::<fc::FunctionComponent>(item) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}
}
#[proc_macro]
pub fn format_args_f(input: TokenStream) -> TokenStream {
use ifmt::*;
@ -238,7 +183,17 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// ```
#[proc_macro]
pub fn rsx(s: TokenStream) -> TokenStream {
match syn::parse::<rsx::RsxBody>(s) {
match syn::parse::<rsx::RsxBody<AS_RSX>>(s) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}
}
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
/// We aim to keep functional parity with html templates.
#[proc_macro]
pub fn html(s: TokenStream) -> TokenStream {
match syn::parse::<rsx::RsxBody<AS_HTML>>(s) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}

View file

@ -15,17 +15,17 @@ use syn::{
Error, Ident, LitStr, Result, Token,
};
pub enum AmbiguousElement {
Element(Element),
Component(Component),
pub enum AmbiguousElement<const AS: HTML_OR_RSX> {
Element(Element<AS>),
Component(Component<AS>),
}
impl Parse for AmbiguousElement {
impl Parse for AmbiguousElement<AS_RSX> {
fn parse(input: ParseStream) -> Result<Self> {
// Try to parse as an absolute path and immediately defer to the componetn
if input.peek(Token![::]) {
return input
.parse::<Component>()
.parse::<Component<AS_RSX>>()
.map(|c| AmbiguousElement::Component(c));
}
@ -34,7 +34,7 @@ impl Parse for AmbiguousElement {
if let Ok(pat) = input.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return input
.parse::<Component>()
.parse::<Component<AS_RSX>>()
.map(|c| AmbiguousElement::Component(c));
}
}
@ -45,11 +45,11 @@ impl Parse for AmbiguousElement {
let first_char = name_str.chars().next().unwrap();
if first_char.is_ascii_uppercase() {
input
.parse::<Component>()
.parse::<Component<AS_RSX>>()
.map(|c| AmbiguousElement::Component(c))
} else {
input
.parse::<Element>()
.parse::<Element<AS_RSX>>()
.map(|c| AmbiguousElement::Element(c))
}
} else {
@ -61,7 +61,48 @@ impl Parse for AmbiguousElement {
}
}
impl ToTokens for AmbiguousElement {
impl Parse for AmbiguousElement<AS_HTML> {
fn parse(input: ParseStream) -> Result<Self> {
// Try to parse as an absolute path and immediately defer to the componetn
if input.peek(Token![::]) {
return input
.parse::<Component<AS_HTML>>()
.map(|c| AmbiguousElement::Component(c));
}
// If not an absolute path, then parse the ident and check if it's a valid tag
if let Ok(pat) = input.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return input
.parse::<Component<AS_HTML>>()
.map(|c| AmbiguousElement::Component(c));
}
}
if let Ok(name) = input.fork().parse::<Ident>() {
let name_str = name.to_string();
let first_char = name_str.chars().next().unwrap();
if first_char.is_ascii_uppercase() {
input
.parse::<Component<AS_HTML>>()
.map(|c| AmbiguousElement::Component(c))
} else {
input
.parse::<Element<AS_HTML>>()
.map(|c| AmbiguousElement::Element(c))
}
} else {
if input.peek(LitStr) {
panic!("it's actually a litstr");
}
Err(Error::new(input.span(), "Not a valid Html tag"))
}
}
}
impl<const AS: HTML_OR_RSX> ToTokens for AmbiguousElement<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
AmbiguousElement::Element(el) => el.to_tokens(tokens),

View file

@ -0,0 +1,131 @@
use crate::util::is_valid_tag;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
Error, Ident, Result, Token,
};
use super::*;
pub struct RsxBody<const AS: HTML_OR_RSX> {
custom_context: Option<Ident>,
roots: Vec<BodyNode<AS>>,
}
/// The custom rusty variant of parsing rsx!
impl Parse for RsxBody<AS_RSX> {
fn parse(input: ParseStream) -> Result<Self> {
// if input.peek(LitStr) {
// return input.parse::<LitStr>()?.parse::<RsxRender>();
// }
// try to parse the first ident and comma
let custom_context =
if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
let _ = input.parse::<Token![in]>()?;
let name = input.parse::<Ident>()?;
if is_valid_tag(&name.to_string()) {
return Err(Error::new(
input.span(),
"Custom context cannot be an html element name",
));
} else {
input.parse::<Token![,]>().unwrap();
Some(name)
}
} else {
None
};
let mut body = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
let cfg: BodyParseConfig<AS_RSX> = BodyParseConfig {
allow_children: true,
allow_fields: false,
allow_manual_props: false,
};
cfg.parse_component_body(input, &mut body, &mut children, &mut manual_props)?;
Ok(Self {
roots: children,
custom_context,
})
}
}
/// The HTML variant of parsing rsx!
impl Parse for RsxBody<AS_HTML> {
fn parse(input: ParseStream) -> Result<Self> {
// if input.peek(LitStr) {
// return input.parse::<LitStr>()?.parse::<RsxRender>();
// }
// try to parse the first ident and comma
let custom_context =
if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
let _ = input.parse::<Token![in]>()?;
let name = input.parse::<Ident>()?;
if is_valid_tag(&name.to_string()) {
return Err(Error::new(
input.span(),
"Custom context cannot be an html element name",
));
} else {
input.parse::<Token![,]>().unwrap();
Some(name)
}
} else {
None
};
let mut body = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
let cfg: BodyParseConfig<AS_HTML> = BodyParseConfig {
allow_children: true,
allow_fields: false,
allow_manual_props: false,
};
cfg.parse_component_body(input, &mut body, &mut children, &mut manual_props)?;
Ok(Self {
roots: children,
custom_context,
})
}
}
/// Serialize the same way, regardless of flavor
impl<const A: HTML_OR_RSX> ToTokens for RsxBody<A> {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let inner = if self.roots.len() == 1 {
let inner = &self.roots[0];
quote! {#inner}
} else {
let childs = &self.roots;
quote! { __cx.fragment_from_iter([ #(#childs),* ]) }
};
match &self.custom_context {
// The `in cx` pattern allows directly rendering
Some(ident) => out_tokens.append_all(quote! {
#ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
use dioxus_elements::GlobalAttributes;
#inner
}))
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
use dioxus_elements::GlobalAttributes;
#inner
})
}),
};
}
}

View file

@ -19,18 +19,18 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
token, Expr, ExprClosure, Ident, Result, Token,
token, Error, Expr, ExprClosure, Ident, Result, Token,
};
pub struct Component {
pub struct Component<const AS: HTML_OR_RSX> {
// accept any path-like argument
name: syn::Path,
body: Vec<ComponentField>,
children: Vec<BodyNode>,
body: Vec<ComponentField<AS>>,
children: Vec<BodyNode<AS>>,
manual_props: Option<Expr>,
}
impl Parse for Component {
impl Parse for Component<AS_RSX> {
fn parse(stream: ParseStream) -> Result<Self> {
// let name = s.parse::<syn::ExprPath>()?;
// todo: look into somehow getting the crate/super/etc
@ -41,21 +41,45 @@ impl Parse for Component {
let content: ParseBuffer;
syn::braced!(content in stream);
let mut body: Vec<ComponentField> = Vec::new();
let mut children: Vec<BodyNode> = Vec::new();
let mut body: Vec<ComponentField<AS_RSX>> = Vec::new();
let mut children: Vec<BodyNode<AS_RSX>> = Vec::new();
let mut manual_props = None;
parse_component_body(
&content,
&BodyParseConfig {
allow_children: true,
allow_fields: true,
allow_manual_props: true,
},
&mut body,
&mut children,
&mut manual_props,
)?;
let cfg: BodyParseConfig<AS_RSX> = BodyParseConfig {
allow_children: true,
allow_fields: true,
allow_manual_props: true,
};
cfg.parse_component_body(&content, &mut body, &mut children, &mut manual_props)?;
Ok(Self {
name,
body,
children,
manual_props,
})
}
}
impl Parse for Component<AS_HTML> {
fn parse(stream: ParseStream) -> Result<Self> {
let name = syn::Path::parse_mod_style(stream)?;
// parse the guts
let content: ParseBuffer;
syn::braced!(content in stream);
let mut body: Vec<ComponentField<AS_HTML>> = Vec::new();
let mut children: Vec<BodyNode<AS_HTML>> = Vec::new();
let mut manual_props = None;
let cfg: BodyParseConfig<AS_HTML> = BodyParseConfig {
allow_children: true,
allow_fields: true,
allow_manual_props: true,
};
cfg.parse_component_body(&content, &mut body, &mut children, &mut manual_props)?;
Ok(Self {
name,
@ -66,64 +90,117 @@ impl Parse for Component {
}
}
pub struct BodyParseConfig {
pub struct BodyParseConfig<const AS: HTML_OR_RSX> {
pub allow_fields: bool,
pub allow_children: bool,
pub allow_manual_props: bool,
}
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
content: &ParseBuffer,
cfg: &BodyParseConfig,
body: &mut Vec<ComponentField>,
children: &mut Vec<BodyNode>,
manual_props: &mut Option<Expr>,
) -> Result<()> {
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
if content.peek(Token![..]) {
if !cfg.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
impl BodyParseConfig<AS_RSX> {
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
&self,
content: &ParseBuffer,
body: &mut Vec<ComponentField<AS_RSX>>,
children: &mut Vec<BodyNode<AS_RSX>>,
manual_props: &mut Option<Expr>,
) -> Result<()> {
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
content.parse::<Token![..]>()?;
*manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !cfg.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField>()?);
} else {
if !cfg.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
if content.peek(Token![..]) {
if !self.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
}
content.parse::<Token![..]>()?;
*manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !self.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField<AS_RSX>>()?);
} else {
if !self.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode<AS_RSX>>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(())
}
}
impl BodyParseConfig<AS_HTML> {
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
&self,
content: &ParseBuffer,
body: &mut Vec<ComponentField<AS_HTML>>,
children: &mut Vec<BodyNode<AS_HTML>>,
manual_props: &mut Option<Expr>,
) -> Result<()> {
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
if content.peek(Token![..]) {
if !self.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
}
content.parse::<Token![..]>()?;
*manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !self.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField<AS_HTML>>()?);
} else {
if !self.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode<AS_HTML>>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(())
}
Ok(())
}
impl ToTokens for Component {
impl<const AS: HTML_OR_RSX> ToTokens for Component<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
@ -195,7 +272,7 @@ impl ToTokens for Component {
}
// the struct's fields info
pub struct ComponentField {
pub struct ComponentField<const AS: HTML_OR_RSX> {
name: Ident,
content: ContentField,
}
@ -222,7 +299,28 @@ impl ToTokens for ContentField {
}
}
impl Parse for ComponentField {
impl Parse for ComponentField<AS_RSX> {
fn parse(input: ParseStream) -> Result<Self> {
let name = Ident::parse_any(input)?;
input.parse::<Token![:]>()?;
let name_str = name.to_string();
let content = if name_str.starts_with("on") {
if input.peek(token::Brace) {
let content;
syn::braced!(content in input);
ContentField::OnHandlerRaw(content.parse()?)
} else {
ContentField::OnHandler(input.parse()?)
}
} else {
ContentField::ManExpr(input.parse::<Expr>()?)
};
Ok(Self { name, content })
}
}
impl Parse for ComponentField<AS_HTML> {
fn parse(input: ParseStream) -> Result<Self> {
let name = Ident::parse_any(input)?;
input.parse::<Token![:]>()?;
@ -244,7 +342,7 @@ impl Parse for ComponentField {
}
}
impl ToTokens for ComponentField {
impl<const AS: HTML_OR_RSX> ToTokens for ComponentField<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ComponentField { name, content, .. } = self;
tokens.append_all(quote! {

View file

@ -11,35 +11,16 @@ use syn::{
// =======================================
// Parse the VNode::Element type
// =======================================
pub struct Element {
pub struct Element<const AS: HTML_OR_RSX> {
name: Ident,
key: Option<AttrType>,
attributes: Vec<ElementAttr>,
listeners: Vec<ElementAttr>,
children: Vec<BodyNode>,
children: Vec<BodyNode<AS>>,
is_static: bool,
}
impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let attr = &self.attributes;
let childs = &self.children;
let listeners = &self.listeners;
tokens.append_all(quote! {
__cx.element(
dioxus_elements::#name,
__cx.bump().alloc([ #(#listeners),* ]),
__cx.bump().alloc([ #(#attr),* ]),
__cx.bump().alloc([ #(#childs),* ]),
None,
)
});
}
}
impl Parse for Element {
impl Parse for Element<AS_RSX> {
fn parse(stream: ParseStream) -> Result<Self> {
let name = Ident::parse(stream)?;
@ -49,7 +30,7 @@ impl Parse for Element {
let mut attributes: Vec<ElementAttr> = vec![];
let mut listeners: Vec<ElementAttr> = vec![];
let mut children: Vec<BodyNode> = vec![];
let mut children: Vec<BodyNode<AS_RSX>> = vec![];
let mut key = None;
'parsing: loop {
@ -67,7 +48,7 @@ impl Parse for Element {
name.clone(),
)?;
} else {
children.push(content.parse::<BodyNode>()?);
children.push(content.parse::<BodyNode<AS_RSX>>()?);
}
// consume comma if it exists
@ -88,6 +69,74 @@ impl Parse for Element {
}
}
impl Parse for Element<AS_HTML> {
fn parse(stream: ParseStream) -> Result<Self> {
let name = Ident::parse(stream)?;
// parse the guts
let content: ParseBuffer;
syn::braced!(content in stream);
let mut attributes: Vec<ElementAttr> = vec![];
let mut listeners: Vec<ElementAttr> = vec![];
let mut children: Vec<BodyNode<AS_HTML>> = vec![];
let mut key = None;
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
parse_element_body(
&content,
&mut attributes,
&mut listeners,
&mut key,
name.clone(),
)?;
} else {
children.push(content.parse::<BodyNode<AS_HTML>>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas after elements/text
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self {
key,
name,
attributes,
children,
listeners,
is_static: false,
})
}
}
impl<const AS: HTML_OR_RSX> ToTokens for Element<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let attr = &self.attributes;
let childs = &self.children;
let listeners = &self.listeners;
tokens.append_all(quote! {
__cx.element(
dioxus_elements::#name,
__cx.bump().alloc([ #(#listeners),* ]),
__cx.bump().alloc([ #(#attr),* ]),
__cx.bump().alloc([ #(#childs),* ]),
None,
)
});
}
}
/// =======================================
/// Parse a VElement's Attributes
/// =======================================

View file

@ -10,7 +10,7 @@
use syn::parse::ParseBuffer;
use super::AmbiguousElement;
use super::{AmbiguousElement, AS_HTML, AS_RSX, HTML_OR_RSX};
use {
proc_macro::TokenStream,
@ -23,10 +23,11 @@ use {
},
};
pub struct Fragment {
children: Vec<AmbiguousElement>,
pub struct Fragment<const AS: HTML_OR_RSX> {
children: Vec<AmbiguousElement<AS>>,
}
impl Parse for Fragment {
impl Parse for Fragment<AS_RSX> {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<Ident>()?;
@ -36,7 +37,7 @@ impl Parse for Fragment {
let content: ParseBuffer;
syn::braced!(content in input);
while !content.is_empty() {
content.parse::<AmbiguousElement>()?;
content.parse::<AmbiguousElement<AS_RSX>>()?;
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
@ -46,7 +47,27 @@ impl Parse for Fragment {
}
}
impl ToTokens for Fragment {
impl Parse for Fragment<AS_HTML> {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<Ident>()?;
let children = Vec::new();
// parse the guts
let content: ParseBuffer;
syn::braced!(content in input);
while !content.is_empty() {
content.parse::<AmbiguousElement<AS_HTML>>()?;
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self { children })
}
}
impl<const AS: HTML_OR_RSX> ToTokens for Fragment<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let childs = &self.children;
let children = quote! {

View file

@ -12,6 +12,7 @@
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
mod ambiguous;
mod body;
mod component;
mod element;
mod fragment;
@ -19,97 +20,12 @@ mod node;
// Re-export the namespaces into each other
pub use ambiguous::*;
pub use body::*;
pub use component::*;
pub use element::*;
pub use fragment::*;
pub use node::*;
use crate::util::is_valid_tag;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
Error, Ident, LitStr, Result, Token,
};
pub struct RsxBody {
custom_context: Option<Ident>,
roots: Vec<BodyNode>,
}
impl Parse for RsxBody {
fn parse(input: ParseStream) -> Result<Self> {
// if input.peek(LitStr) {
// return input.parse::<LitStr>()?.parse::<RsxRender>();
// }
// try to parse the first ident and comma
let custom_context =
if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
let _ = input.parse::<Token![in]>()?;
let name = input.parse::<Ident>()?;
if is_valid_tag(&name.to_string()) {
return Err(Error::new(
input.span(),
"Custom context cannot be an html element name",
));
} else {
input.parse::<Token![,]>().unwrap();
Some(name)
}
} else {
None
};
let mut body = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
parse_component_body(
input,
&BodyParseConfig {
allow_children: true,
allow_fields: false,
allow_manual_props: false,
},
&mut body,
&mut children,
&mut manual_props,
)?;
Ok(Self {
roots: children,
custom_context,
})
}
}
impl ToTokens for RsxBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let inner = if self.roots.len() == 1 {
let inner = &self.roots[0];
quote! {#inner}
} else {
let childs = &self.roots;
quote! { __cx.fragment_from_iter([ #(#childs),* ]) }
};
match &self.custom_context {
// The `in cx` pattern allows directly rendering
Some(ident) => out_tokens.append_all(quote! {
#ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
use dioxus_elements::GlobalAttributes;
#inner
}))
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
use dioxus_elements::GlobalAttributes;
#inner
})
}),
};
}
}
pub type HTML_OR_RSX = bool;
pub const AS_HTML: bool = true;
pub const AS_RSX: bool = false;

View file

@ -10,13 +10,13 @@ use syn::{
// ==============================================
// Parse any div {} as a VElement
// ==============================================
pub enum BodyNode {
Element(AmbiguousElement),
pub enum BodyNode<const AS: HTML_OR_RSX> {
Element(AmbiguousElement<AS>),
Text(TextNode),
RawExpr(Expr),
}
impl Parse for BodyNode {
impl Parse for BodyNode<AS_RSX> {
fn parse(stream: ParseStream) -> Result<Self> {
// Supposedly this approach is discouraged due to inability to return proper errors
// TODO: Rework this to provide more informative errors
@ -31,11 +31,33 @@ impl Parse for BodyNode {
return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
}
Ok(BodyNode::Element(stream.parse::<AmbiguousElement>()?))
Ok(BodyNode::Element(
stream.parse::<AmbiguousElement<AS_RSX>>()?,
))
}
}
impl Parse for BodyNode<AS_HTML> {
fn parse(stream: ParseStream) -> Result<Self> {
// Supposedly this approach is discouraged due to inability to return proper errors
// TODO: Rework this to provide more informative errors
if stream.peek(token::Brace) {
let content;
syn::braced!(content in stream);
return Ok(BodyNode::RawExpr(content.parse::<Expr>()?));
}
if stream.peek(LitStr) {
return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
}
Ok(BodyNode::Element(
stream.parse::<AmbiguousElement<AS_HTML>>()?,
))
}
}
impl ToTokens for BodyNode {
impl<const AS: HTML_OR_RSX> ToTokens for BodyNode<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self {
BodyNode::Element(el) => el.to_tokens(tokens),