mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
wip: serious refactor with const generics
This commit is contained in:
parent
308414f5ef
commit
160d86abbe
16 changed files with 492 additions and 286 deletions
|
@ -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);
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
131
packages/core-macro/src/rsx/body.rs
Normal file
131
packages/core-macro/src/rsx/body.rs
Normal 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
|
||||
})
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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! {
|
||||
|
|
|
@ -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
|
||||
/// =======================================
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue