awesome: arbitrary expressions excepted without braces
This commit is contained in:
1 parent
14961023f9
commit
4c85bcfdc8
40 files changed
+825
-767
No files matched your search
@@ -16,6 +16,7 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1.8"
|
||||
proc-macro-error = "1.0.4"
|
||||
proc-macro2 = { version = "1.0.6" }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
|
||||
@@ -99,6 +99,7 @@ impl ToTokens for InlinePropsBody {
|
||||
};
|
||||
|
||||
out_tokens.append_all(quote! {
|
||||
#[allow(non_camel_case)]
|
||||
#modifiers
|
||||
#vis struct #struct_name #generics {
|
||||
#(#fields),*
|
||||
|
||||
@@ -177,11 +177,12 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_error::proc_macro_error]
|
||||
#[proc_macro]
|
||||
pub fn rsx(s: TokenStream) -> TokenStream {
|
||||
match syn::parse::<rsx::CallBody>(s) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
Ok(stream) => stream.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
//! Parse anything that has a pattern of < Ident, Bracket >
|
||||
//! ========================================================
|
||||
//!
|
||||
//! Whenever a `name {}` pattern emerges, we need to parse it into an element, a component, or a fragment.
|
||||
//! This feature must support:
|
||||
//! - Namepsaced/pathed components
|
||||
//! - Differentiating between built-in and custom elements
|
||||
|
||||
use super::*;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::ToTokens;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Error, Ident, Result, Token,
|
||||
};
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum AmbiguousElement {
|
||||
Element(Element),
|
||||
Component(Component),
|
||||
}
|
||||
|
||||
impl Parse for AmbiguousElement {
|
||||
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>().map(AmbiguousElement::Component);
|
||||
}
|
||||
|
||||
// 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>().map(AmbiguousElement::Component);
|
||||
}
|
||||
}
|
||||
|
||||
use syn::ext::IdentExt;
|
||||
if let Ok(name) = input.fork().call(Ident::parse_any) {
|
||||
let name_str = name.to_string();
|
||||
|
||||
let first_char = name_str.chars().next().unwrap();
|
||||
if first_char.is_ascii_uppercase() {
|
||||
input.parse::<Component>().map(AmbiguousElement::Component)
|
||||
} else {
|
||||
if input.peek2(syn::token::Paren) {
|
||||
input.parse::<Component>().map(AmbiguousElement::Component)
|
||||
} else {
|
||||
input.parse::<Element>().map(AmbiguousElement::Element)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(input.span(), "Not a valid Html tag"))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToTokens for AmbiguousElement {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
AmbiguousElement::Element(el) => el.to_tokens(tokens),
|
||||
AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Ident, Result, Token,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct CallBody {
|
||||
custom_context: Option<Ident>,
|
||||
roots: Vec<BodyNode>,
|
||||
}
|
||||
|
||||
/// The custom rusty variant of parsing rsx!
|
||||
impl Parse for CallBody {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let custom_context = try_parse_custom_context(input)?;
|
||||
let (_, roots, _) = BodyConfig::new_call_body().parse_component_body(input)?;
|
||||
Ok(Self {
|
||||
custom_context,
|
||||
roots,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
|
||||
let res = if input.peek(Ident) && input.peek2(Token![,]) {
|
||||
let name = input.parse::<Ident>()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Serialize the same way, regardless of flavor
|
||||
impl ToTokens for CallBody {
|
||||
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_root([ #(#childs),* ]) }
|
||||
};
|
||||
|
||||
match &self.custom_context {
|
||||
// The `in cx` pattern allows directly rendering
|
||||
Some(ident) => out_tokens.append_all(quote! {
|
||||
#ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
}))
|
||||
}),
|
||||
// Otherwise we just build the LazyNode wrapper
|
||||
None => out_tokens.append_all(quote! {
|
||||
LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
})
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,10 @@ use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
token, Error, Expr, ExprClosure, Ident, Result, Token,
|
||||
token, Expr, ExprClosure, Ident, Result, Token,
|
||||
};
|
||||
|
||||
pub struct Component {
|
||||
// accept any path-like argument
|
||||
name: syn::Path,
|
||||
body: Vec<ComponentField>,
|
||||
children: Vec<BodyNode>,
|
||||
@@ -32,12 +31,8 @@ pub struct Component {
|
||||
|
||||
impl Parse for Component {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
// let name = s.parse::<syn::ExprPath>()?;
|
||||
// todo: look into somehow getting the crate/super/etc
|
||||
|
||||
let name = syn::Path::parse_mod_style(stream)?;
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
|
||||
// if we see a `{` then we have a block
|
||||
@@ -48,13 +43,25 @@ impl Parse for Component {
|
||||
syn::parenthesized!(content in stream);
|
||||
}
|
||||
|
||||
let cfg: BodyConfig = BodyConfig {
|
||||
allow_children: true,
|
||||
allow_fields: true,
|
||||
allow_manual_props: true,
|
||||
};
|
||||
let mut body = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
let (body, children, manual_props) = cfg.parse_component_body(&content)?;
|
||||
while !content.is_empty() {
|
||||
// if we splat into a component then we're merging properties
|
||||
if content.peek(Token![..]) {
|
||||
content.parse::<Token![..]>()?;
|
||||
manual_props = Some(content.parse::<Expr>()?);
|
||||
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
body.push(content.parse::<ComponentField>()?);
|
||||
} else {
|
||||
children.push(content.parse::<BodyNode>()?);
|
||||
}
|
||||
|
||||
if content.peek(Token![,]) {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
@@ -65,77 +72,6 @@ impl Parse for Component {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BodyConfig {
|
||||
pub allow_fields: bool,
|
||||
pub allow_children: bool,
|
||||
pub allow_manual_props: bool,
|
||||
}
|
||||
|
||||
impl BodyConfig {
|
||||
/// The configuration to parse the root
|
||||
pub fn new_call_body() -> Self {
|
||||
Self {
|
||||
allow_children: true,
|
||||
allow_fields: false,
|
||||
allow_manual_props: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BodyConfig {
|
||||
// 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,
|
||||
) -> Result<(Vec<ComponentField>, Vec<BodyNode>, Option<Expr>)> {
|
||||
let mut body = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
'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>()?);
|
||||
} else {
|
||||
if !self.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![,]>();
|
||||
}
|
||||
}
|
||||
Ok((body, children, manual_props))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Component {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name;
|
||||
@@ -219,9 +155,7 @@ pub struct ComponentField {
|
||||
|
||||
enum ContentField {
|
||||
ManExpr(Expr),
|
||||
OnHandler(ExprClosure),
|
||||
|
||||
// A handler was provided in {} tokens
|
||||
OnHandlerRaw(Expr),
|
||||
}
|
||||
|
||||
@@ -229,9 +163,6 @@ impl ToTokens for ContentField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
ContentField::ManExpr(e) => e.to_tokens(tokens),
|
||||
ContentField::OnHandler(e) => tokens.append_all(quote! {
|
||||
__cx.bump().alloc(#e)
|
||||
}),
|
||||
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
|
||||
__cx.bump().alloc(#e)
|
||||
}),
|
||||
@@ -246,13 +177,7 @@ impl Parse for ComponentField {
|
||||
|
||||
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()?)
|
||||
}
|
||||
ContentField::OnHandlerRaw(input.parse()?)
|
||||
} else {
|
||||
ContentField::ManExpr(input.parse::<Expr>()?)
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
token, Expr, ExprClosure, Ident, LitStr, Result, Token,
|
||||
Expr, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
// =======================================
|
||||
@@ -33,45 +33,69 @@ impl Parse for Element {
|
||||
let mut key = None;
|
||||
let mut _el_ref = None;
|
||||
|
||||
// todo: more descriptive error handling
|
||||
while !content.is_empty() {
|
||||
// parse fields with commas
|
||||
// break when we don't get this pattern anymore
|
||||
// start parsing bodynodes
|
||||
// "def": 456,
|
||||
// abc: 123,
|
||||
loop {
|
||||
// Parse the raw literal fields
|
||||
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
let name = content.parse::<LitStr>()?;
|
||||
let ident = name.clone();
|
||||
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
if content.peek(LitStr) {
|
||||
let value = content.parse::<LitStr>()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::CustomAttrText { name, value },
|
||||
});
|
||||
} else {
|
||||
let value = content.parse::<Expr>()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::CustomAttrExpression { name, value },
|
||||
});
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// todo: add a message saying you need to include commas between fields
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
proc_macro_error::emit_error!(
|
||||
ident,
|
||||
"This attribute is misisng a trailing comma"
|
||||
)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
let name = content.parse::<Ident>()?;
|
||||
let ident = name.clone();
|
||||
|
||||
let name_str = name.to_string();
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
if name_str.starts_with("on") {
|
||||
if content.peek(token::Brace) {
|
||||
let mycontent;
|
||||
syn::braced!(mycontent in content);
|
||||
|
||||
listeners.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::EventTokens {
|
||||
name,
|
||||
tokens: mycontent.parse()?,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
listeners.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::EventClosure {
|
||||
name,
|
||||
closure: content.parse()?,
|
||||
},
|
||||
});
|
||||
};
|
||||
listeners.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::EventTokens {
|
||||
name,
|
||||
tokens: content.parse()?,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
match name_str.as_str() {
|
||||
"key" => {
|
||||
key = Some(content.parse()?);
|
||||
}
|
||||
"classes" => {
|
||||
todo!("custom class list not supported")
|
||||
}
|
||||
"namespace" => {
|
||||
todo!("custom namespace not supported")
|
||||
}
|
||||
"classes" => todo!("custom class list not supported yet"),
|
||||
// "namespace" => todo!("custom namespace not supported yet"),
|
||||
"node_ref" => {
|
||||
_el_ref = Some(content.parse::<Expr>()?);
|
||||
}
|
||||
@@ -96,27 +120,56 @@ impl Parse for Element {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if content.peek(LitStr) && content.peek2(Token![:]) {
|
||||
let name = content.parse::<LitStr>()?;
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
if content.peek(LitStr) {
|
||||
let value = content.parse::<LitStr>()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::CustomAttrText { name, value },
|
||||
});
|
||||
} else {
|
||||
let value = content.parse::<Expr>()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::CustomAttrExpression { name, value },
|
||||
});
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
children.push(content.parse::<BodyNode>()?);
|
||||
|
||||
// todo: add a message saying you need to include commas between fields
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
proc_macro_error::emit_error!(
|
||||
ident,
|
||||
"This attribute is misisng a trailing comma"
|
||||
)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while !content.is_empty() {
|
||||
if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
||||
let ident = content.parse::<LitStr>().unwrap();
|
||||
let name = ident.value();
|
||||
proc_macro_error::emit_error!(
|
||||
ident, "This attribute `{}` is in the wrong place.", name;
|
||||
help =
|
||||
"All attribute fields must be placed above children elements.
|
||||
|
||||
div {
|
||||
attr: \"...\", <---- attribute is above children
|
||||
div { } <---- children are below attributes
|
||||
}";
|
||||
)
|
||||
}
|
||||
|
||||
if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
||||
let ident = content.parse::<Ident>().unwrap();
|
||||
let name = ident.to_string();
|
||||
proc_macro_error::emit_error!(
|
||||
ident, "This attribute `{}` is in the wrong place.", name;
|
||||
help =
|
||||
"All attribute fields must be placed above children elements.
|
||||
|
||||
div {
|
||||
attr: \"...\", <---- attribute is above children
|
||||
div { } <---- children are below attributes
|
||||
}";
|
||||
)
|
||||
}
|
||||
|
||||
children.push(content.parse::<BodyNode>()?);
|
||||
// consume comma if it exists
|
||||
// we don't actually care if there *are* commas after elements/text
|
||||
if content.peek(Token![,]) {
|
||||
@@ -138,7 +191,7 @@ impl Parse for Element {
|
||||
impl ToTokens for Element {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name;
|
||||
let childs = &self.children;
|
||||
let children = &self.children;
|
||||
|
||||
let listeners = &self.listeners;
|
||||
let attr = &self.attributes;
|
||||
@@ -153,7 +206,7 @@ impl ToTokens for Element {
|
||||
dioxus_elements::#name,
|
||||
[ #(#listeners),* ],
|
||||
[ #(#attr),* ],
|
||||
[ #(#childs),* ],
|
||||
[ #(#children),* ],
|
||||
#key,
|
||||
)
|
||||
});
|
||||
@@ -173,8 +226,8 @@ enum ElementAttr {
|
||||
// "attribute": true,
|
||||
CustomAttrExpression { name: LitStr, value: Expr },
|
||||
|
||||
// onclick: move |_| {}
|
||||
EventClosure { name: Ident, closure: ExprClosure },
|
||||
// // onclick: move |_| {}
|
||||
// EventClosure { name: Ident, closure: ExprClosure },
|
||||
|
||||
// onclick: {}
|
||||
EventTokens { name: Ident, tokens: Expr },
|
||||
@@ -189,7 +242,7 @@ impl ToTokens for ElementAttrNamed {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let ElementAttrNamed { el_name, attr } = self;
|
||||
|
||||
let toks = match attr {
|
||||
tokens.append_all(match attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
quote! {
|
||||
dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
|
||||
@@ -200,26 +253,26 @@ impl ToTokens for ElementAttrNamed {
|
||||
dioxus_elements::#el_name.#name(__cx, #value)
|
||||
}
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
|
||||
}
|
||||
|
||||
ElementAttr::EventClosure { name, closure } => {
|
||||
quote! {
|
||||
dioxus_elements::on::#name(__cx, #closure)
|
||||
__cx.attr( #name, format_args_f!(#value), None, false )
|
||||
}
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
quote! {
|
||||
__cx.attr( #name, format_args_f!(#value), None, false )
|
||||
}
|
||||
}
|
||||
// ElementAttr::EventClosure { name, closure } => {
|
||||
// quote! {
|
||||
// dioxus_elements::on::#name(__cx, #closure)
|
||||
// }
|
||||
// }
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
quote! {
|
||||
dioxus_elements::on::#name(__cx, #tokens)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokens.append_all(toks);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//! Parse `Fragments` into the Fragment VNode
|
||||
//! ==========================================
|
||||
//!
|
||||
//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the Fragment format.
|
||||
//! We can be reasonably sure that whatever enters this parsing path is in the right format.
|
||||
//! This feature must support:
|
||||
//! - [x] Optional commas
|
||||
//! - [ ] Children
|
||||
//! - [ ] Keys
|
||||
|
||||
use super::AmbiguousElement;
|
||||
use syn::parse::ParseBuffer;
|
||||
use {
|
||||
proc_macro2::TokenStream as TokenStream2,
|
||||
quote::{quote, ToTokens, TokenStreamExt},
|
||||
syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Ident, Result, Token,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Fragment {
|
||||
children: Vec<AmbiguousElement>,
|
||||
}
|
||||
|
||||
impl Parse for Fragment {
|
||||
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>()?;
|
||||
|
||||
if content.peek(Token![,]) {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
Ok(Self { children })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Fragment {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let childs = &self.children;
|
||||
let children = quote! {
|
||||
ChildrenList::new(__cx)
|
||||
#( .add_child(#childs) )*
|
||||
.finish()
|
||||
};
|
||||
tokens.append_all(quote! {
|
||||
// #key_token,
|
||||
dioxus::builder::vfragment(
|
||||
__cx,
|
||||
None,
|
||||
#children
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,85 @@
|
||||
//!
|
||||
//! 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;
|
||||
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::*;
|
||||
|
||||
// imports
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Ident, Result, Token,
|
||||
};
|
||||
|
||||
pub struct CallBody {
|
||||
custom_context: Option<Ident>,
|
||||
roots: Vec<BodyNode>,
|
||||
}
|
||||
|
||||
impl Parse for CallBody {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let custom_context = if input.peek(Ident) && input.peek2(Token![,]) {
|
||||
let name = input.parse::<Ident>()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut roots = Vec::new();
|
||||
|
||||
while !input.is_empty() {
|
||||
let node = input.parse::<BodyNode>()?;
|
||||
|
||||
if input.peek(Token![,]) {
|
||||
let _ = input.parse::<Token![,]>();
|
||||
}
|
||||
|
||||
roots.push(node);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
custom_context,
|
||||
roots,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize the same way, regardless of flavor
|
||||
impl ToTokens for CallBody {
|
||||
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_root([ #(#childs),* ]) }
|
||||
};
|
||||
|
||||
match &self.custom_context {
|
||||
// The `in cx` pattern allows directly rendering
|
||||
Some(ident) => out_tokens.append_all(quote! {
|
||||
#ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
}))
|
||||
}),
|
||||
|
||||
// Otherwise we just build the LazyNode wrapper
|
||||
None => out_tokens.append_all(quote! {
|
||||
LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
})
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,34 +4,67 @@ use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
token, Expr, LitStr, Result,
|
||||
token, Expr, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
// ==============================================
|
||||
// Parse any div {} as a VElement
|
||||
// ==============================================
|
||||
/*
|
||||
Parse
|
||||
-> div {}
|
||||
-> Component {}
|
||||
-> component()
|
||||
-> "text {with_args}"
|
||||
-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr
|
||||
*/
|
||||
pub enum BodyNode {
|
||||
Element(AmbiguousElement),
|
||||
Text(TextNode),
|
||||
Element(Element),
|
||||
Component(Component),
|
||||
Text(LitStr),
|
||||
RawExpr(Expr),
|
||||
}
|
||||
|
||||
impl Parse for BodyNode {
|
||||
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>()?));
|
||||
return Ok(BodyNode::Text(stream.parse()?));
|
||||
}
|
||||
|
||||
Ok(BodyNode::Element(stream.parse::<AmbiguousElement>()?))
|
||||
// div {} -> el
|
||||
// Div {} -> comp
|
||||
if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
|
||||
if stream
|
||||
.fork()
|
||||
.parse::<Ident>()?
|
||||
.to_string()
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_ascii_uppercase()
|
||||
{
|
||||
return Ok(BodyNode::Component(stream.parse()?));
|
||||
} else {
|
||||
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
||||
}
|
||||
}
|
||||
|
||||
// component() -> comp
|
||||
// ::component {} -> comp
|
||||
// ::component () -> comp
|
||||
if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
|
||||
|| (stream.peek(Token![::]))
|
||||
|| (stream.peek(Token![:]) && stream.peek2(Token![:]))
|
||||
{
|
||||
return Ok(BodyNode::Component(stream.parse::<Component>()?));
|
||||
}
|
||||
|
||||
// crate::component{} -> comp
|
||||
// crate::component() -> comp
|
||||
if let Ok(pat) = stream.fork().parse::<syn::Path>() {
|
||||
if pat.segments.len() > 1 {
|
||||
return Ok(BodyNode::Component(stream.parse::<Component>()?));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,31 +72,13 @@ impl ToTokens for BodyNode {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match &self {
|
||||
BodyNode::Element(el) => el.to_tokens(tokens),
|
||||
BodyNode::Text(txt) => txt.to_tokens(tokens),
|
||||
BodyNode::Component(comp) => comp.to_tokens(tokens),
|
||||
BodyNode::Text(txt) => tokens.append_all(quote! {
|
||||
__cx.text(format_args_f!(#txt))
|
||||
}),
|
||||
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
|
||||
__cx.fragment_from_iter(#exp)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Parse just plain text
|
||||
// =======================================
|
||||
pub struct TextNode(LitStr);
|
||||
|
||||
impl Parse for TextNode {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
Ok(Self(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TextNode {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
// todo: use heuristics to see if we can promote to &static str
|
||||
let token_stream = &self.0.to_token_stream();
|
||||
tokens.append_all(quote! {
|
||||
__cx.text(format_args_f!(#token_stream))
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -734,9 +734,9 @@ impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVNode<'_> for &'static str {
|
||||
impl<'b> IntoVNode<'_> for &'b str {
|
||||
fn into_vnode(self, cx: NodeFactory) -> VNode {
|
||||
cx.static_text(self)
|
||||
cx.text(format_args!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ fn lists_work() {
|
||||
static App: Component = |cx| {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
{(0..6).map(|f| rsx!(span{ "{f}" }))}
|
||||
(0..6).map(|f| rsx!(span{ "{f}" }))
|
||||
))
|
||||
};
|
||||
let mut vdom = VirtualDom::new(App);
|
||||
|
||||
@@ -215,3 +215,67 @@ impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<'a,
|
||||
!*self.get()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Convenience methods for UseState.
|
||||
|
||||
Note!
|
||||
|
||||
This is not comprehensive.
|
||||
This is *just* meant to make common operations easier.
|
||||
*/
|
||||
|
||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
||||
|
||||
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
|
||||
type Output = T;
|
||||
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
self.inner.current_val.add(rhs)
|
||||
}
|
||||
}
|
||||
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
|
||||
fn add_assign(&mut self, rhs: T) {
|
||||
self.set(self.inner.current_val.add(rhs));
|
||||
}
|
||||
}
|
||||
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
|
||||
type Output = T;
|
||||
|
||||
fn sub(self, rhs: T) -> Self::Output {
|
||||
self.inner.current_val.sub(rhs)
|
||||
}
|
||||
}
|
||||
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
|
||||
fn sub_assign(&mut self, rhs: T) {
|
||||
self.set(self.inner.current_val.sub(rhs));
|
||||
}
|
||||
}
|
||||
|
||||
/// MUL
|
||||
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
|
||||
type Output = T;
|
||||
|
||||
fn mul(self, rhs: T) -> Self::Output {
|
||||
self.inner.current_val.mul(rhs)
|
||||
}
|
||||
}
|
||||
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
|
||||
fn mul_assign(&mut self, rhs: T) {
|
||||
self.set(self.inner.current_val.mul(rhs));
|
||||
}
|
||||
}
|
||||
/// DIV
|
||||
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
|
||||
type Output = T;
|
||||
|
||||
fn div(self, rhs: T) -> Self::Output {
|
||||
self.inner.current_val.div(rhs)
|
||||
}
|
||||
}
|
||||
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
|
||||
fn div_assign(&mut self, rhs: T) {
|
||||
self.set(self.inner.current_val.div(rhs));
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,8 @@ pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
|
||||
a {
|
||||
href: "#",
|
||||
class: format_args!("{}", cx.props.class.unwrap_or("")),
|
||||
{&cx.props.children}
|
||||
onclick: move |_| service.push_route(cx.props.to.clone()),
|
||||
{&cx.props.children}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -309,19 +309,18 @@ mod tests {
|
||||
|
||||
static SLIGHTLY_MORE_COMPLEX: Component = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
title: "About W3Schools"
|
||||
{(0..20).map(|f| rsx!{
|
||||
div { title: "About W3Schools",
|
||||
(0..20).map(|f| rsx!{
|
||||
div {
|
||||
title: "About W3Schools"
|
||||
style: "color:blue;text-align:center"
|
||||
class: "About W3Schools"
|
||||
title: "About W3Schools",
|
||||
style: "color:blue;text-align:center",
|
||||
class: "About W3Schools",
|
||||
p {
|
||||
title: "About W3Schools"
|
||||
title: "About W3Schools",
|
||||
"Hello world!: {f}"
|
||||
}
|
||||
}
|
||||
})}
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user