wip: move macro lib out of proc macro crate

This commit is contained in:
Jonathan Kelley committed 2022-01-17 16:37:44 -05:00
1 parent c08d04449f
commit abfac0d59b
16 files changed
+116 -47

No files matched your search

-443
View File
@@ -1,443 +0,0 @@
//!
//! TODO:
//! - [ ] Support for VComponents
//! - [ ] Support for inline format in text
//! - [ ] Support for expressions in attribute positions
//! - [ ] Support for iterators
//! - [ ] support for inline html!
//!
//!
//!
//!
//!
//!
//!
use {
proc_macro2::TokenStream as TokenStream2,
quote::{quote, ToTokens, TokenStreamExt},
syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token,
},
};
// ==============================================
// Parse any stream coming from the html! macro
// ==============================================
pub struct HtmlRender {
kind: NodeOrList,
}
impl Parse for HtmlRender {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(LitStr) {
return input.parse::<LitStr>()?.parse::<HtmlRender>();
}
// let __cx: Ident = s.parse()?;
// s.parse::<Token![,]>()?;
// if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
let kind = if input.peek(token::Bracket) {
let nodes_toks;
syn::bracketed!(nodes_toks in input);
let mut nodes: Vec<MaybeExpr<Node>> = vec![nodes_toks.parse()?];
while nodes_toks.peek(Token![,]) {
nodes_toks.parse::<Token![,]>()?;
nodes.push(nodes_toks.parse()?);
}
NodeOrList::List(NodeList(nodes))
} else {
NodeOrList::Node(input.parse()?)
};
Ok(HtmlRender { kind })
}
}
impl ToTokens for HtmlRender {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
// create a lazy tree that accepts a bump allocator
let final_tokens = quote! {
dioxus::prelude::LazyNodes::new(move |__cx| {
let bump = __cx.bump();
#new_toks
})
};
final_tokens.to_tokens(out_tokens);
}
}
/// =============================================
/// Parse any child as a node or list of nodes
/// =============================================
/// - [ ] Allow iterators
///
///
enum NodeOrList {
Node(Node),
List(NodeList),
}
impl ToTokens for ToToksCtx<&NodeOrList> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self.inner {
NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens),
NodeOrList::List(list) => self.recurse(list).to_tokens(tokens),
}
}
}
struct NodeList(Vec<MaybeExpr<Node>>);
impl ToTokens for ToToksCtx<&NodeList> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let nodes = self.inner.0.iter().map(|node| self.recurse(node));
tokens.append_all(quote! {
dioxus::bumpalo::vec![in bump;
#(#nodes),*
]
});
}
}
enum Node {
Element(Element),
Text(TextNode),
}
impl ToTokens for ToToksCtx<&Node> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self.inner {
Node::Element(el) => self.recurse(el).to_tokens(tokens),
Node::Text(txt) => self.recurse(txt).to_tokens(tokens),
}
}
}
impl Node {
fn _peek(s: ParseStream) -> bool {
(s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr)
}
}
impl Parse for Node {
fn parse(s: ParseStream) -> Result<Self> {
Ok(if s.peek(Token![<]) {
Node::Element(s.parse()?)
} else {
Node::Text(s.parse()?)
})
}
}
/// =======================================
/// Parse the VNode::Element type
/// =======================================
/// - [ ] Allow VComponent
///
///
struct Element {
name: Ident,
attrs: Vec<Attr>,
children: MaybeExpr<Vec<Node>>,
}
impl ToTokens for ToToksCtx<&Element> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
// let __cx = self.__cx;
let name = &self.inner.name;
// let name = &self.inner.name.to_string();
tokens.append_all(quote! {
__cx.element(dioxus_elements::#name)
// dioxus::builder::ElementBuilder::new( #name)
});
for attr in self.inner.attrs.iter() {
self.recurse(attr).to_tokens(tokens);
}
// if is_valid_svg_tag(&name.to_string()) {
// tokens.append_all(quote! {
// .namespace(Some("http://www.w3.org/2000/svg"))
// });
// }
match &self.inner.children {
MaybeExpr::Expr(expr) => tokens.append_all(quote! {
.children(#expr)
}),
MaybeExpr::Literal(nodes) => {
let mut children = nodes.iter();
if let Some(child) = children.next() {
let mut inner_toks = TokenStream2::new();
self.recurse(child).to_tokens(&mut inner_toks);
for child in children {
quote!(,).to_tokens(&mut inner_toks);
self.recurse(child).to_tokens(&mut inner_toks);
}
tokens.append_all(quote! {
.children([#inner_toks])
});
}
}
}
tokens.append_all(quote! {
.finish()
});
}
}
impl Parse for Element {
fn parse(s: ParseStream) -> Result<Self> {
s.parse::<Token![<]>()?;
let name = Ident::parse_any(s)?;
let mut attrs = vec![];
let _children: Vec<Node> = vec![];
// keep looking for attributes
while !s.peek(Token![>]) {
// self-closing
if s.peek(Token![/]) {
s.parse::<Token![/]>()?;
s.parse::<Token![>]>()?;
return Ok(Self {
name,
attrs,
children: MaybeExpr::Literal(vec![]),
});
}
attrs.push(s.parse()?);
}
s.parse::<Token![>]>()?;
// Contents of an element can either be a brace (in which case we just copy verbatim), or a
// sequence of nodes.
let children = if s.peek(token::Brace) {
// expr
let content;
syn::braced!(content in s);
MaybeExpr::Expr(content.parse()?)
} else {
// nodes
let mut children = vec![];
while !(s.peek(Token![<]) && s.peek2(Token![/])) {
children.push(s.parse()?);
}
MaybeExpr::Literal(children)
};
// closing element
s.parse::<Token![<]>()?;
s.parse::<Token![/]>()?;
let close = Ident::parse_any(s)?;
if close != name {
return Err(Error::new_spanned(
close,
"closing element does not match opening",
));
}
s.parse::<Token![>]>()?;
Ok(Self {
name,
attrs,
children,
})
}
}
/// =======================================
/// Parse a VElement's Attributes
/// =======================================
/// - [ ] Allow expressions as attribute
///
///
struct Attr {
name: Ident,
ty: AttrType,
}
impl Parse for Attr {
fn parse(s: ParseStream) -> Result<Self> {
let mut name = Ident::parse_any(s)?;
let name_str = name.to_string();
s.parse::<Token![=]>()?;
// Check if this is an event handler
// If so, parse into literal tokens
let ty = if name_str.starts_with("on") {
// remove the "on" bit
name = Ident::new(name_str.trim_start_matches("on"), name.span());
let content;
syn::braced!(content in s);
// AttrType::Value(content.parse()?)
AttrType::Event(content.parse()?)
// AttrType::Event(content.parse()?)
} else {
let lit_str = if name_str == "style" && s.peek(token::Brace) {
// special-case to deal with literal styles.
let outer;
syn::braced!(outer in s);
// double brace for inline style.
// todo!("Style support not ready yet");
// if outer.peek(token::Brace) {
// let inner;
// syn::braced!(inner in outer);
// let styles: Styles = inner.parse()?;
// MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
// } else {
// just parse as an expression
MaybeExpr::Expr(outer.parse()?)
// }
} else {
s.parse()?
};
AttrType::Value(lit_str)
};
Ok(Attr { name, ty })
}
}
impl ToTokens for ToToksCtx<&Attr> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = self.inner.name.to_string();
let _attr_stream = TokenStream2::new();
match &self.inner.ty {
AttrType::Value(value) => {
let value = self.recurse(value);
if name == "xmlns" {
tokens.append_all(quote! {
.namespace(Some(#value))
});
} else {
tokens.append_all(quote! {
.attr(#name, format_args_f!(#value))
});
}
}
AttrType::Event(event) => {
tokens.append_all(quote! {
.on(#name, #event)
});
}
}
}
}
enum AttrType {
Value(MaybeExpr<LitStr>),
Event(ExprClosure),
// todo Bool(MaybeExpr<LitBool>)
}
/// =======================================
/// Parse just plain text
/// =======================================
/// - [ ] Perform formatting automatically
///
///
struct TextNode(MaybeExpr<LitStr>);
impl Parse for TextNode {
fn parse(s: ParseStream) -> Result<Self> {
Ok(Self(s.parse()?))
}
}
impl ToTokens for ToToksCtx<&TextNode> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let mut token_stream = TokenStream2::new();
self.recurse(&self.inner.0).to_tokens(&mut token_stream);
tokens.append_all(quote! {
__cx.text(format_args_f!(#token_stream))
});
}
}
#[allow(clippy::large_enum_variant)]
enum MaybeExpr<T> {
Literal(T),
Expr(Expr),
}
impl<T: Parse> Parse for MaybeExpr<T> {
fn parse(s: ParseStream) -> Result<Self> {
if s.peek(token::Brace) {
let content;
syn::braced!(content in s);
Ok(MaybeExpr::Expr(content.parse()?))
} else {
Ok(MaybeExpr::Literal(s.parse()?))
}
}
}
impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr<T>>
where
T: 'a,
ToToksCtx<&'a T>: ToTokens,
{
fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self.inner {
MaybeExpr::Literal(v) => self.recurse(v).to_tokens(tokens),
MaybeExpr::Expr(expr) => expr.to_tokens(tokens),
}
}
}
/// ToTokens context
struct ToToksCtx<T> {
inner: T,
}
impl<'a, T> ToToksCtx<T> {
fn new(inner: T) -> Self {
ToToksCtx { inner }
}
fn recurse<U>(&self, inner: U) -> ToToksCtx<U> {
ToToksCtx { inner }
}
}
impl ToTokens for ToToksCtx<&LitStr> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.inner.to_tokens(tokens)
}
}
#[cfg(test)]
mod test {
fn parse(input: &str) -> super::Result<super::HtmlRender> {
syn::parse_str(input)
}
#[test]
fn div() {
parse("bump, <div class=\"test\"/>").unwrap();
}
#[test]
fn nested() {
parse("bump, <div class=\"test\"><div />\"text\"</div>").unwrap();
}
#[test]
fn complex() {
parse(
"bump,
<section style={{
display: flex;
flex-direction: column;
max-width: 95%;
}} class=\"map-panel\">{contact_details}</section>
",
)
.unwrap();
}
}
-228
View File
@@ -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))
}
-114
View File
@@ -1,114 +0,0 @@
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
token, Block, FnArg, Generics, Ident, Pat, Result, ReturnType, Token, Visibility,
};
pub struct InlinePropsBody {
pub vis: syn::Visibility,
pub fn_token: Token![fn],
pub ident: Ident,
pub cx_token: Box<Pat>,
pub generics: Generics,
pub paren_token: token::Paren,
pub inputs: Punctuated<FnArg, Token![,]>,
// pub fields: FieldsNamed,
pub output: ReturnType,
pub block: Box<Block>,
}
/// The custom rusty variant of parsing rsx!
impl Parse for InlinePropsBody {
fn parse(input: ParseStream) -> Result<Self> {
let vis: Visibility = input.parse()?;
let fn_token = input.parse()?;
let ident = input.parse()?;
let generics = input.parse()?;
let content;
let paren_token = syn::parenthesized!(content in input);
let first_arg: FnArg = content.parse()?;
let cx_token = {
match first_arg {
FnArg::Receiver(_) => panic!("first argument must not be a reciver argument"),
FnArg::Typed(f) => f.pat,
}
};
let _: Result<Token![,]> = content.parse();
let inputs = syn::punctuated::Punctuated::parse_terminated(&content)?;
let output = input.parse()?;
let block = input.parse()?;
Ok(Self {
vis,
fn_token,
ident,
generics,
paren_token,
inputs,
output,
block,
cx_token,
})
}
}
/// Serialize the same way, regardless of flavor
impl ToTokens for InlinePropsBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let Self {
vis,
ident,
generics,
inputs,
output,
block,
cx_token,
..
} = self;
let fields = inputs.iter().map(|f| {
quote! { #vis #f }
});
let struct_name = Ident::new(&format!("{}Props", ident), Span::call_site());
let field_names = inputs.iter().filter_map(|f| match f {
FnArg::Receiver(_) => todo!(),
FnArg::Typed(t) => Some(&t.pat),
});
let modifiers = if generics.params.is_empty() {
quote! { #[derive(Props, PartialEq)] }
} else {
quote! { #[derive(Props)] }
};
let lifetime = if generics.params.is_empty() {
quote! {}
} else {
quote! { 'a, }
};
out_tokens.append_all(quote! {
#modifiers
#[allow(non_camel_case_types)]
#vis struct #struct_name #generics {
#(#fields),*
}
#vis fn #ident #generics (#cx_token: Scope<#lifetime #struct_name #generics>) #output {
let #struct_name { #(#field_names),* } = &cx.props;
#block
}
});
}
}
+1 -6
View File
@@ -1,13 +1,8 @@
use dioxus_macro_inner::*;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::parse_macro_input;
pub(crate) mod ifmt;
pub(crate) mod inlineprops;
pub(crate) mod props;
pub(crate) mod router;
pub(crate) mod rsx;
#[proc_macro]
pub fn format_args_f(input: TokenStream) -> TokenStream {
use ifmt::*;
File diff suppressed because it is too large. Load diff
-214
View File
@@ -1,214 +0,0 @@
#![allow(dead_code)]
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Fields, Ident, LitStr, Variant};
const AT_ATTR_IDENT: &str = "at";
const NOT_FOUND_ATTR_IDENT: &str = "not_found";
pub struct Routable {
ident: Ident,
ats: Vec<LitStr>,
variants: Punctuated<Variant, syn::token::Comma>,
not_found_route: Option<Ident>,
}
impl Parse for Routable {
fn parse(input: ParseStream) -> syn::Result<Self> {
let DeriveInput { ident, data, .. } = input.parse()?;
let data = match data {
Data::Enum(data) => data,
Data::Struct(s) => {
return Err(syn::Error::new(
s.struct_token.span(),
"expected enum, found struct",
))
}
Data::Union(u) => {
return Err(syn::Error::new(
u.union_token.span(),
"expected enum, found union",
))
}
};
let (not_found_route, ats) = parse_variants_attributes(&data.variants)?;
Ok(Self {
ident,
variants: data.variants,
ats,
not_found_route,
})
}
}
fn parse_variants_attributes(
variants: &Punctuated<Variant, syn::token::Comma>,
) -> syn::Result<(Option<Ident>, Vec<LitStr>)> {
let mut not_founds = vec![];
let mut ats: Vec<LitStr> = vec![];
let mut not_found_attrs = vec![];
for variant in variants.iter() {
if let Fields::Unnamed(ref field) = variant.fields {
return Err(syn::Error::new(
field.span(),
"only named fields are supported",
));
}
let attrs = &variant.attrs;
let at_attrs = attrs
.iter()
.filter(|attr| attr.path.is_ident(AT_ATTR_IDENT))
.collect::<Vec<_>>();
let attr = match at_attrs.len() {
1 => *at_attrs.first().unwrap(),
0 => {
return Err(syn::Error::new(
variant.span(),
format!(
"{} attribute must be present on every variant",
AT_ATTR_IDENT
),
))
}
_ => {
return Err(syn::Error::new_spanned(
quote! { #(#at_attrs)* },
format!("only one {} attribute must be present", AT_ATTR_IDENT),
))
}
};
let lit = attr.parse_args::<LitStr>()?;
ats.push(lit);
for attr in attrs.iter() {
if attr.path.is_ident(NOT_FOUND_ATTR_IDENT) {
not_found_attrs.push(attr);
not_founds.push(variant.ident.clone())
}
}
}
if not_founds.len() > 1 {
return Err(syn::Error::new_spanned(
quote! { #(#not_found_attrs)* },
format!("there can only be one {}", NOT_FOUND_ATTR_IDENT),
));
}
Ok((not_founds.into_iter().next(), ats))
}
impl Routable {
// fn build_from_path(&self) -> TokenStream {
// let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
// let ident = &variant.ident;
// let right = match &variant.fields {
// Fields::Unit => quote! { Self::#ident },
// Fields::Named(field) => {
// let fields = field.named.iter().map(|it| {
// //named fields have idents
// it.ident.as_ref().unwrap()
// });
// quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
// }
// Fields::Unnamed(_) => unreachable!(), // already checked
// };
// let left = self.ats.get(i).unwrap();
// quote! {
// #left => ::std::option::Option::Some(#right)
// }
// });
// quote! {
// fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
// match path {
// #(#from_path_matches),*,
// _ => ::std::option::Option::None,
// }
// }
// }
// }
// fn build_to_path(&self) -> TokenStream {
// let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
// let ident = &variant.ident;
// let mut right = self.ats.get(i).unwrap().value();
// match &variant.fields {
// Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
// Fields::Named(field) => {
// let fields = field
// .named
// .iter()
// .map(|it| it.ident.as_ref().unwrap())
// .collect::<Vec<_>>();
// for field in fields.iter() {
// // :param -> {param}
// // so we can pass it to `format!("...", param)`
// right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
// }
// quote! {
// Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
// }
// }
// Fields::Unnamed(_) => unreachable!(), // already checked
// }
// });
// quote! {
// fn to_path(&self) -> ::std::string::String {
// match self {
// #(#to_path_matches),*,
// }
// }
// }
// }
}
pub fn routable_derive_impl(input: Routable) -> TokenStream {
let Routable {
// ats,
// not_found_route,
// ident,
..
} = &input;
// let from_path = input.build_from_path();
// let to_path = input.build_to_path();
quote! {
// #[automatically_derived]
// impl ::dioxus::router::Routable for #ident {
// fn recognize(pathname: &str) -> ::std::option::Option<Self> {
// todo!()
// // ::std::thread_local! {
// // static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
// // }
// // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
// // {
// // let route = ::std::clone::Clone::clone(&route);
// // #cache_thread_local_ident.with(move |val| {
// // *val.borrow_mut() = route;
// // });
// // }
// // route
// }
// }
}
}
-231
View File
@@ -1,231 +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 {
name: syn::Path,
body: Vec<ComponentField>,
children: Vec<BodyNode>,
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 },
};
tokens.append_all(quote! {
__cx.component(
#name,
#builder,
#key_token,
)
})
}
}
// 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) {
let item = input.parse::<LitStr>().unwrap();
proc_macro_error::emit_error!(item, "This attribute is missing a trailing comma")
}
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
}
-278
View File
@@ -1,278 +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 {
name: Ident,
key: Option<LitStr>,
attributes: Vec<ElementAttrNamed>,
listeners: Vec<ElementAttrNamed>,
children: Vec<BodyNode>,
_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 listeners: 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;
}
// 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 missing 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") {
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 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() {
proc_macro_error::emit_error!(
ident,
"This attribute is missing 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![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self {
key,
name: el_name,
attributes,
children,
listeners,
_is_static: false,
})
}
}
impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let children = &self.children;
let listeners = &self.listeners;
let attr = &self.attributes;
let key = match &self.key {
Some(ty) => quote! { Some(format_args_f!(#ty)) },
None => quote! { None },
};
tokens.append_all(quote! {
__cx.element(
dioxus_elements::#name,
__cx.bump().alloc([ #(#listeners),* ]),
__cx.bump().alloc([ #(#attr),* ]),
__cx.bump().alloc([ #(#children),* ]),
#key,
)
});
}
}
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 },
}
struct ElementAttrNamed {
el_name: Ident,
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)
}
}
});
}
}
-95
View File
@@ -1,95 +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.
mod component;
mod element;
mod node;
// 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 {
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
})
}),
};
}
}
-84
View File
@@ -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)
}),
}
}
}
-84
View File
@@ -1,84 +0,0 @@
use crate::{rsx::RsxBody, util::is_valid_svg_tag};
use {
proc_macro::TokenStream,
proc_macro2::{Span, TokenStream as TokenStream2},
quote::{quote, ToTokens, TokenStreamExt},
syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
token, Error, Expr, ExprClosure, Ident, LitBool, LitStr, Path, Result, Token,
},
};
// ==============================================
// Parse any stream coming from the html! macro
// ==============================================
pub struct RsxTemplate {
inner: RsxBody,
}
impl Parse for RsxTemplate {
fn parse(s: ParseStream) -> Result<Self> {
if s.peek(LitStr) {
use std::str::FromStr;
let lit = s.parse::<LitStr>()?;
let g = lit.span();
let mut value = lit.value();
if value.ends_with('\n') {
value.pop();
if value.ends_with('\r') {
value.pop();
}
}
let lit = LitStr::new(&value, lit.span());
// panic!("{:#?}", lit);
match lit.parse::<crate::rsx::RsxBody>() {
Ok(r) => Ok(Self { inner: r }),
Err(e) => Err(e),
}
} else {
panic!("Not a str lit")
}
// let t = s.parse::<LitStr>()?;
// let new_stream = TokenStream::from(t.to_s)
// let cx: Ident = s.parse()?;
// s.parse::<Token![,]>()?;
// if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
// let kind = if s.peek(token::Bracket) {
// let nodes_toks;
// syn::bracketed!(nodes_toks in s);
// let mut nodes: Vec<MaybeExpr<Node>> = vec![nodes_toks.parse()?];
// while nodes_toks.peek(Token![,]) {
// nodes_toks.parse::<Token![,]>()?;
// nodes.push(nodes_toks.parse()?);
// }
// NodeOrList::List(NodeList(nodes))
// } else {
// NodeOrList::Node(s.parse()?)
// };
// Ok(HtmlRender { kind })
}
}
impl ToTokens for RsxTemplate {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
self.inner.to_tokens(out_tokens);
// let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
// // create a lazy tree that accepts a bump allocator
// let final_tokens = quote! {
// dioxus::prelude::LazyNodes::new(move |cx| {
// let bump = &cx.bump();
// #new_toks
// })
// };
// final_tokens.to_tokens(out_tokens);
}
}