WIP hot reload
This commit is contained in:
1 parent
284860f470
commit
c7c84da8ab
19 files changed
+2531
-693
No files matched your search
@@ -1,228 +0,0 @@
|
||||
use ::quote::{quote, ToTokens};
|
||||
use ::std::ops::Not;
|
||||
use ::syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
*,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
|
||||
let IfmtInput {
|
||||
mut format_literal,
|
||||
mut positional_args,
|
||||
mut named_args,
|
||||
} = input;
|
||||
|
||||
let s = format_literal.value();
|
||||
let out_format_literal = &mut String::with_capacity(s.len());
|
||||
|
||||
let mut iterator = s.char_indices().peekable();
|
||||
while let Some((i, c)) = iterator.next() {
|
||||
out_format_literal.push(c);
|
||||
if c != '{' {
|
||||
continue;
|
||||
}
|
||||
// encountered `{`, let's see if it was `{{`
|
||||
if let Some(&(_, '{')) = iterator.peek() {
|
||||
let _ = iterator.next();
|
||||
out_format_literal.push('{');
|
||||
continue;
|
||||
}
|
||||
let (end, colon_or_closing_brace) =
|
||||
iterator
|
||||
.find(|&(_, c)| c == '}' || c == ':')
|
||||
.expect(concat!(
|
||||
"Invalid format string literal\n",
|
||||
"note: if you intended to print `{`, ",
|
||||
"you can escape it using `{{`",
|
||||
));
|
||||
// We use defer to ensure all the `continue`s append the closing char.
|
||||
let mut out_format_literal = defer(&mut *out_format_literal, |it| {
|
||||
it.push(colon_or_closing_brace)
|
||||
});
|
||||
let out_format_literal: &mut String = *out_format_literal;
|
||||
let mut arg = s[i + 1..end].trim();
|
||||
if let Some("=") = arg.get(arg.len().saturating_sub(1)..) {
|
||||
assert_eq!(
|
||||
out_format_literal.pop(), // Remove the opening brace
|
||||
Some('{'),
|
||||
);
|
||||
arg = &arg[..arg.len() - 1];
|
||||
out_format_literal.push_str(arg);
|
||||
out_format_literal.push_str(" = {");
|
||||
}
|
||||
if arg.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
enum Segment {
|
||||
Ident(Ident),
|
||||
LitInt(LitInt),
|
||||
}
|
||||
let segments: Vec<Segment> = {
|
||||
impl Parse for Segment {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(Ident) {
|
||||
input.parse().map(Segment::Ident)
|
||||
} else if lookahead.peek(LitInt) {
|
||||
input.parse().map(Segment::LitInt)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
match ::syn::parse::Parser::parse_str(
|
||||
Punctuated::<Segment, Token![.]>::parse_separated_nonempty,
|
||||
arg,
|
||||
) {
|
||||
Ok(segments) => segments.into_iter().collect(),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
};
|
||||
match segments.len() {
|
||||
0 => unreachable!("`parse_separated_nonempty` returned empty"),
|
||||
1 => {
|
||||
out_format_literal.push_str(arg);
|
||||
match { segments }.pop().unwrap() {
|
||||
Segment::LitInt(_) => {
|
||||
// found something like `{0}`, let `format_args!`
|
||||
// handle it.
|
||||
continue;
|
||||
}
|
||||
Segment::Ident(ident) => {
|
||||
// if `ident = ...` is not yet among the extra args
|
||||
if named_args.iter().all(|(it, _)| *it != ident) {
|
||||
named_args.push((
|
||||
ident.clone(),
|
||||
parse_quote!(#ident), // Expr
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
::std::fmt::Write::write_fmt(
|
||||
out_format_literal,
|
||||
format_args!("{}", positional_args.len()),
|
||||
)
|
||||
.expect("`usize` or `char` Display impl cannot panic");
|
||||
let segments: Punctuated<TokenStream, Token![.]> = segments
|
||||
.into_iter()
|
||||
.map(|it| match it {
|
||||
Segment::Ident(ident) => ident.into_token_stream(),
|
||||
Segment::LitInt(literal) => literal.into_token_stream(),
|
||||
})
|
||||
.collect();
|
||||
positional_args.push(parse_quote! {
|
||||
#segments
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let named_args = named_args.into_iter().map(|(ident, expr)| {
|
||||
quote! {
|
||||
#ident = #expr
|
||||
}
|
||||
});
|
||||
format_literal = LitStr::new(out_format_literal, format_literal.span());
|
||||
|
||||
Ok(quote! {
|
||||
format_args!(
|
||||
#format_literal
|
||||
#(, #positional_args)*
|
||||
#(, #named_args)*
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
||||
pub struct IfmtInput {
|
||||
format_literal: LitStr,
|
||||
positional_args: Vec<Expr>,
|
||||
named_args: Vec<(Ident, Expr)>,
|
||||
}
|
||||
|
||||
impl Parse for IfmtInput {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let format_literal = input.parse()?;
|
||||
let mut positional_args = vec![];
|
||||
loop {
|
||||
if input.parse::<Option<Token![,]>>()?.is_none() {
|
||||
return Ok(Self {
|
||||
format_literal,
|
||||
positional_args,
|
||||
named_args: vec![],
|
||||
});
|
||||
}
|
||||
if input.peek(Ident) && input.peek2(Token![=]) && input.peek3(Token![=]).not() {
|
||||
// Found a positional parameter
|
||||
break;
|
||||
}
|
||||
positional_args.push(input.parse()?);
|
||||
}
|
||||
let named_args = Punctuated::<_, Token![,]>::parse_terminated_with(input, |input| {
|
||||
Ok({
|
||||
let name: Ident = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let expr: Expr = input.parse()?;
|
||||
(name, expr)
|
||||
})
|
||||
})?
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok(Self {
|
||||
format_literal,
|
||||
positional_args,
|
||||
named_args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn defer<'a, T: 'a, Drop: 'a>(x: T, drop: Drop) -> impl ::core::ops::DerefMut<Target = T> + 'a
|
||||
where
|
||||
Drop: FnOnce(T),
|
||||
{
|
||||
use ::core::mem::ManuallyDrop;
|
||||
struct Ret<T, Drop>(ManuallyDrop<T>, ManuallyDrop<Drop>)
|
||||
where
|
||||
Drop: FnOnce(T);
|
||||
impl<T, Drop> ::core::ops::Drop for Ret<T, Drop>
|
||||
where
|
||||
Drop: FnOnce(T),
|
||||
{
|
||||
fn drop(&'_ mut self) {
|
||||
use ::core::ptr;
|
||||
unsafe {
|
||||
// # Safety
|
||||
//
|
||||
// - This is the canonical example of using `ManuallyDrop`.
|
||||
let value = ManuallyDrop::into_inner(ptr::read(&self.0));
|
||||
let drop = ManuallyDrop::into_inner(ptr::read(&self.1));
|
||||
drop(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T, Drop> ::core::ops::Deref for Ret<T, Drop>
|
||||
where
|
||||
Drop: FnOnce(T),
|
||||
{
|
||||
type Target = T;
|
||||
#[inline]
|
||||
fn deref(&'_ self) -> &'_ Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<T, Drop> ::core::ops::DerefMut for Ret<T, Drop>
|
||||
where
|
||||
Drop: FnOnce(T),
|
||||
{
|
||||
#[inline]
|
||||
fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop))
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use rsx::{BodyNode, Component, ContentField, ElementAttr, IfmtInput};
|
||||
use std::collections::HashMap;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
mod ifmt;
|
||||
mod inlineprops;
|
||||
mod props;
|
||||
|
||||
@@ -11,7 +12,7 @@ use dioxus_rsx as rsx;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
||||
use ifmt::*;
|
||||
use rsx::*;
|
||||
let item = parse_macro_input!(input as IfmtInput);
|
||||
format_args_f_impl(item)
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
@@ -183,7 +184,17 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
||||
pub fn rsx(s: TokenStream) -> TokenStream {
|
||||
match syn::parse::<rsx::CallBody>(s) {
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
Ok(stream) => stream.to_token_stream().into(),
|
||||
Ok(body) => {
|
||||
#[cfg(feature = "hot_reload")]
|
||||
{
|
||||
use dioxus_rsx_interperter::captuered_context::CapturedContextBuilder;
|
||||
|
||||
let captured = CapturedContextBuilder::from_call_body(body);
|
||||
captured.to_token_stream().into()
|
||||
}
|
||||
#[cfg(not(feature = "hot_reload"))]
|
||||
body.to_token_stream().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
//! Parse components into the VComponent VNode
|
||||
//! ==========================================
|
||||
//!
|
||||
//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the vcomponent format.
|
||||
//! We can be reasonably sure that whatever enters this parsing path is in the right format.
|
||||
//! This feature must support
|
||||
//! - [x] Namespaced components
|
||||
//! - [x] Fields
|
||||
//! - [x] Componentbuilder synax
|
||||
//! - [x] Optional commas
|
||||
//! - [ ] Children
|
||||
//! - [ ] Keys
|
||||
//! - [ ] Properties spreading with with `..` syntax
|
||||
|
||||
use super::*;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
token, Expr, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
pub struct Component {
|
||||
pub name: syn::Path,
|
||||
pub body: Vec<ComponentField>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub manual_props: Option<Expr>,
|
||||
}
|
||||
|
||||
impl Parse for Component {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
let name = syn::Path::parse_mod_style(stream)?;
|
||||
|
||||
let content: ParseBuffer;
|
||||
|
||||
// if we see a `{` then we have a block
|
||||
// else parse as a function-like call
|
||||
if stream.peek(token::Brace) {
|
||||
syn::braced!(content in stream);
|
||||
} else {
|
||||
syn::parenthesized!(content in stream);
|
||||
}
|
||||
|
||||
let mut body = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
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,
|
||||
body,
|
||||
children,
|
||||
manual_props,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Component {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name;
|
||||
|
||||
let mut has_key = None;
|
||||
|
||||
let builder = match &self.manual_props {
|
||||
Some(manual_props) => {
|
||||
let mut toks = quote! {
|
||||
let mut __manual_props = #manual_props;
|
||||
};
|
||||
for field in &self.body {
|
||||
if field.name == "key" {
|
||||
has_key = Some(field);
|
||||
} else {
|
||||
let name = &field.name;
|
||||
let val = &field.content;
|
||||
toks.append_all(quote! {
|
||||
__manual_props.#name = #val;
|
||||
});
|
||||
}
|
||||
}
|
||||
toks.append_all(quote! {
|
||||
__manual_props
|
||||
});
|
||||
quote! {{
|
||||
#toks
|
||||
}}
|
||||
}
|
||||
None => {
|
||||
let mut toks = quote! { fc_to_builder(#name) };
|
||||
for field in &self.body {
|
||||
match field.name.to_string().as_str() {
|
||||
"key" => {
|
||||
//
|
||||
has_key = Some(field);
|
||||
}
|
||||
_ => toks.append_all(quote! {#field}),
|
||||
}
|
||||
}
|
||||
|
||||
if !self.children.is_empty() {
|
||||
let childs = &self.children;
|
||||
toks.append_all(quote! {
|
||||
.children(__cx.create_children([ #( #childs ),* ]))
|
||||
});
|
||||
}
|
||||
|
||||
toks.append_all(quote! {
|
||||
.build()
|
||||
});
|
||||
toks
|
||||
}
|
||||
};
|
||||
|
||||
let key_token = match has_key {
|
||||
Some(field) => {
|
||||
let inners = &field.content;
|
||||
quote! { Some(format_args_f!(#inners)) }
|
||||
}
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
let fn_name = self.name.segments.last().unwrap().ident.to_string();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__cx.component(
|
||||
#name,
|
||||
#builder,
|
||||
#key_token,
|
||||
#fn_name
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// the struct's fields info
|
||||
pub struct ComponentField {
|
||||
name: Ident,
|
||||
content: ContentField,
|
||||
}
|
||||
|
||||
enum ContentField {
|
||||
ManExpr(Expr),
|
||||
Formatted(LitStr),
|
||||
OnHandlerRaw(Expr),
|
||||
}
|
||||
|
||||
impl ToTokens for ContentField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
ContentField::ManExpr(e) => e.to_tokens(tokens),
|
||||
ContentField::Formatted(s) => tokens.append_all(quote! {
|
||||
__cx.raw_text(format_args_f!(#s)).0
|
||||
}),
|
||||
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
|
||||
__cx.event_handler(#e)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ComponentField {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let name = Ident::parse_any(input)?;
|
||||
input.parse::<Token![:]>()?;
|
||||
|
||||
if name.to_string().starts_with("on") {
|
||||
let content = ContentField::OnHandlerRaw(input.parse()?);
|
||||
return Ok(Self { name, content });
|
||||
}
|
||||
|
||||
if name == "key" {
|
||||
let content = ContentField::ManExpr(input.parse()?);
|
||||
return Ok(Self { name, content });
|
||||
}
|
||||
|
||||
if input.peek(LitStr) && input.peek2(Token![,]) {
|
||||
let t: LitStr = input.fork().parse()?;
|
||||
|
||||
if is_literal_foramtted(&t) {
|
||||
let content = ContentField::Formatted(input.parse()?);
|
||||
return Ok(Self { name, content });
|
||||
}
|
||||
}
|
||||
|
||||
if input.peek(LitStr) && input.peek2(LitStr) {
|
||||
missing_trailing_comma!(input.span());
|
||||
}
|
||||
|
||||
let content = ContentField::ManExpr(input.parse()?);
|
||||
Ok(Self { name, content })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ComponentField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let ComponentField { name, content, .. } = self;
|
||||
tokens.append_all(quote! {
|
||||
.#name(#content)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_literal_foramtted(lit: &LitStr) -> bool {
|
||||
let s = lit.value();
|
||||
let mut chars = s.chars();
|
||||
|
||||
while let Some(next) = chars.next() {
|
||||
if next == '{' {
|
||||
let nen = chars.next();
|
||||
if nen != Some('{') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
Expr, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
// =======================================
|
||||
// Parse the VNode::Element type
|
||||
// =======================================
|
||||
pub struct Element {
|
||||
pub name: Ident,
|
||||
pub key: Option<LitStr>,
|
||||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub _is_static: bool,
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
let el_name = Ident::parse(stream)?;
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in stream);
|
||||
|
||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||
let mut children: Vec<BodyNode> = vec![];
|
||||
let mut key = None;
|
||||
let mut _el_ref = None;
|
||||
|
||||
// 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) && content.peek2(Token![,]) {
|
||||
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;
|
||||
}
|
||||
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
missing_trailing_comma!(ident);
|
||||
}
|
||||
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") {
|
||||
attributes.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 yet"),
|
||||
// "namespace" => todo!("custom namespace not supported yet"),
|
||||
"node_ref" => {
|
||||
_el_ref = Some(content.parse::<Expr>()?);
|
||||
}
|
||||
_ => {
|
||||
if content.peek(LitStr) {
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::AttrText {
|
||||
name,
|
||||
value: content.parse()?,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::AttrExpression {
|
||||
name,
|
||||
value: content.parse()?,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// todo: add a message saying you need to include commas between fields
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
missing_trailing_comma!(ident);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while !content.is_empty() {
|
||||
if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
||||
attr_after_element!(content.span());
|
||||
}
|
||||
|
||||
if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
||||
attr_after_element!(content.span());
|
||||
}
|
||||
|
||||
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![,]) {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
key,
|
||||
name: el_name,
|
||||
attributes,
|
||||
children,
|
||||
_is_static: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Element {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name;
|
||||
let children = &self.children;
|
||||
|
||||
let key = match &self.key {
|
||||
Some(ty) => quote! { Some(format_args_f!(#ty)) },
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
let listeners = self
|
||||
.attributes
|
||||
.iter()
|
||||
.filter(|f| matches!(f.attr, ElementAttr::EventTokens { .. }));
|
||||
|
||||
let attr = self
|
||||
.attributes
|
||||
.iter()
|
||||
.filter(|f| !matches!(f.attr, ElementAttr::EventTokens { .. }));
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__cx.element(
|
||||
dioxus_elements::#name,
|
||||
__cx.bump().alloc([ #(#listeners),* ]),
|
||||
__cx.bump().alloc([ #(#attr),* ]),
|
||||
__cx.bump().alloc([ #(#children),* ]),
|
||||
#key,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ElementAttr {
|
||||
/// attribute: "valuee {}"
|
||||
AttrText { name: Ident, value: LitStr },
|
||||
|
||||
/// attribute: true,
|
||||
AttrExpression { name: Ident, value: Expr },
|
||||
|
||||
/// "attribute": "value {}"
|
||||
CustomAttrText { name: LitStr, value: LitStr },
|
||||
|
||||
/// "attribute": true,
|
||||
CustomAttrExpression { name: LitStr, value: Expr },
|
||||
|
||||
// /// onclick: move |_| {}
|
||||
// EventClosure { name: Ident, closure: ExprClosure },
|
||||
/// onclick: {}
|
||||
EventTokens { name: Ident, tokens: Expr },
|
||||
}
|
||||
|
||||
pub struct ElementAttrNamed {
|
||||
pub el_name: Ident,
|
||||
pub attr: ElementAttr,
|
||||
}
|
||||
|
||||
impl ToTokens for ElementAttrNamed {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let ElementAttrNamed { el_name, attr } = self;
|
||||
|
||||
tokens.append_all(match attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
quote! {
|
||||
dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
|
||||
}
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
quote! {
|
||||
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)
|
||||
// }
|
||||
// }
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
quote! {
|
||||
dioxus_elements::on::#name(__cx, #tokens)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
macro_rules! missing_trailing_comma {
|
||||
($span:expr) => {
|
||||
proc_macro_error::emit_error!($span, "missing trailing comma")
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! attr_after_element {
|
||||
($span:expr) => {
|
||||
proc_macro_error::emit_error!(
|
||||
$span,
|
||||
"expected element";
|
||||
help = "move the attribute above all the children and text elements"
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
//! Parse the root tokens in the rsx!{} macro
|
||||
//! =========================================
|
||||
//!
|
||||
//! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.
|
||||
//! This feature must support:
|
||||
//! - [x] Optionally rendering if the `in XYZ` pattern is present
|
||||
//! - [x] Fragments as top-level element (through ambiguous)
|
||||
//! - [x] Components as top-level element (through ambiguous)
|
||||
//! - [x] Tags as top-level elements (through ambiguous)
|
||||
//! - [x] Good errors if parsing fails
|
||||
//!
|
||||
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
|
||||
|
||||
#[macro_use]
|
||||
mod errors;
|
||||
|
||||
mod component;
|
||||
mod element;
|
||||
mod node;
|
||||
|
||||
pub mod pretty;
|
||||
|
||||
// Re-export the namespaces into each other
|
||||
pub use component::*;
|
||||
pub use element::*;
|
||||
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 {
|
||||
pub custom_context: Option<Ident>,
|
||||
pub 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(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(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
})
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
token, Expr, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
/*
|
||||
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(Element),
|
||||
Component(Component),
|
||||
Text(LitStr),
|
||||
RawExpr(Expr),
|
||||
}
|
||||
|
||||
impl Parse for BodyNode {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
if stream.peek(LitStr) {
|
||||
return Ok(BodyNode::Text(stream.parse()?));
|
||||
}
|
||||
|
||||
// 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>()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for BodyNode {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match &self {
|
||||
BodyNode::Element(el) => el.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)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
//! pretty printer for rsx!
|
||||
Reference in new issue
Block a user