wip: serious refactor with const generics
This commit is contained in:
1 parent
308414f5ef
commit
160d86abbe
16 files changed
+492
-286
No files matched your search
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user