Feat: major overhaul to diffing

This commit is contained in:
Jonathan Kelley committed 2021-05-15 12:03:08 -04:00
1 parent c809095124
commit 9810feebf5
37 files changed
+1470 -1129

No files matched your search

+205 -191
View File
@@ -1,5 +1,7 @@
use syn::parse::{discouraged::Speculative, ParseBuffer};
use crate::util::is_valid_html_tag;
use {
proc_macro::TokenStream,
proc_macro2::{Span, TokenStream as TokenStream2},
@@ -15,77 +17,105 @@ use {
// Parse any stream coming from the rsx! macro
// ==============================================
pub struct RsxRender {
custom_context: Option<Ident>,
root: RootOption,
}
enum RootOption {
// for lists of components
Fragment(),
Element(Element),
Component(Component),
}
impl ToTokens for RootOption {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
RootOption::Fragment() => todo!(),
RootOption::Element(el) => el.to_tokens(tokens),
RootOption::Component(comp) => comp.to_tokens(tokens),
}
}
// custom_context: Option<Ident>,
root: AmbiguousElement,
}
impl Parse for RsxRender {
fn parse(input: ParseStream) -> Result<Self> {
let fork = input.fork();
let root = { input.parse::<AmbiguousElement>() }?;
if !input.is_empty() {
return Err(Error::new(
input.span(),
"Currently only one element is allowed per component",
));
}
let custom_context = fork
.parse::<Ident>()
.and_then(|ident| {
fork.parse::<Token![,]>().map(|_| {
input.advance_to(&fork);
ident
})
})
.ok();
let forked = input.fork();
let name = forked.parse::<Ident>()?;
let root = match crate::util::is_valid_tag(&name.to_string()) {
true => input.parse::<Element>().map(|el| RootOption::Element(el)),
false => input.parse::<Component>().map(|c| RootOption::Component(c)),
}?;
Ok(Self {
root,
custom_context,
})
Ok(Self { root })
}
}
impl ToTokens for RsxRender {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let new_toks = (&self.root).to_token_stream();
// create a lazy tree that accepts a bump allocator
let final_tokens = match &self.custom_context {
Some(ident) => quote! {
#ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
let bump = ctx.bump;
#new_toks
}))
},
None => quote! {
dioxus::prelude::LazyNodes::new(move |ctx|{
let bump = ctx.bump;
#new_toks
})
},
};
// Currently disabled
//
// let final_tokens = match &self.custom_context {
// Some(ident) => quote! {
// #ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
// let bump = ctx.bump;
// #new_toks
// }))
// },
final_tokens.to_tokens(out_tokens);
let inner = &self.root;
let output = quote! {
dioxus::prelude::LazyNodes::new(move |ctx|{
let bump = ctx.bump;
#inner
})
};
output.to_tokens(out_tokens)
}
}
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(|c| AmbiguousElement::Component(c));
}
// If not an absolute path, then parse the ident and check if it's a valid tag
if let Ok(pat) = input.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return input
.parse::<Component>()
.map(|c| AmbiguousElement::Component(c));
}
}
if let Ok(name) = input.fork().parse::<Ident>() {
let name_str = name.to_string();
match is_valid_html_tag(&name_str) {
true => input
.parse::<Element>()
.map(|c| AmbiguousElement::Element(c)),
false => {
let first_char = name_str.chars().next().unwrap();
if first_char.is_ascii_uppercase() {
input
.parse::<Component>()
.map(|c| AmbiguousElement::Component(c))
} else {
Err(Error::new(
name.span(),
"Components must be uppercased, perhaps you mispelled a html tag",
))
}
}
}
} 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),
}
}
}
@@ -93,9 +123,8 @@ impl ToTokens for RsxRender {
// Parse any div {} as a VElement
// ==============================================
enum Node {
Element(Element),
Element(AmbiguousElement),
Text(TextNode),
Component(Component),
RawExpr(Expr),
}
@@ -104,7 +133,6 @@ impl ToTokens for &Node {
match &self {
Node::Element(el) => el.to_tokens(tokens),
Node::Text(txt) => txt.to_tokens(tokens),
Node::Component(c) => c.to_tokens(tokens),
Node::RawExpr(exp) => exp.to_tokens(tokens),
}
}
@@ -115,31 +143,17 @@ impl Parse for Node {
// Supposedly this approach is discouraged due to inability to return proper errors
// TODO: Rework this to provide more informative errors
let fork = stream.fork();
if let Ok(text) = fork.parse::<TextNode>() {
stream.advance_to(&fork);
return Ok(Self::Text(text));
if stream.peek(token::Brace) {
let content;
syn::braced!(content in stream);
return Ok(Node::RawExpr(content.parse::<Expr>()?));
}
let fork = stream.fork();
if let Ok(element) = fork.parse::<Element>() {
stream.advance_to(&fork);
return Ok(Self::Element(element));
if stream.peek(LitStr) {
return Ok(Node::Text(stream.parse::<TextNode>()?));
}
let fork = stream.fork();
if let Ok(comp) = fork.parse::<Component>() {
stream.advance_to(&fork);
return Ok(Self::Component(comp));
}
let fork = stream.fork();
if let Ok(tok) = try_parse_bracketed(&fork) {
stream.advance_to(&fork);
return Ok(Node::RawExpr(tok));
}
return Err(Error::new(stream.span(), "Failed to parse as a valid node"));
Ok(Node::Element(stream.parse::<AmbiguousElement>()?))
}
}
@@ -156,6 +170,7 @@ impl Parse for Component {
// todo: look into somehow getting the crate/super/etc
let name = syn::Path::parse_mod_style(s)?;
// parse the guts
let content: ParseBuffer;
syn::braced!(content in s);
@@ -169,9 +184,7 @@ impl Parse for Component {
break 'parsing;
}
if let Ok(field) = content.parse::<ComponentField>() {
body.push(field);
}
body.push(content.parse::<ComponentField>()?);
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
@@ -228,6 +241,7 @@ impl ToTokens for &Component {
});
}
}
// the struct's fields info
pub struct ComponentField {
name: Ident,
@@ -258,10 +272,56 @@ impl ToTokens for &ComponentField {
// =======================================
struct Element {
name: Ident,
attrs: Vec<Attr>,
attrs: Vec<ElementAttr>,
children: Vec<Node>,
}
impl Parse for Element {
fn parse(stream: ParseStream) -> Result<Self> {
//
let name = Ident::parse(stream)?;
if !crate::util::is_valid_html_tag(&name.to_string()) {
return Err(Error::new(name.span(), "Not a valid Html tag"));
}
// parse the guts
let content: ParseBuffer;
syn::braced!(content in stream);
let mut attrs: Vec<ElementAttr> = vec![];
let mut children: Vec<Node> = vec![];
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
let forked = content.fork();
if forked.call(Ident::parse_any).is_ok()
&& forked.parse::<Token![:]>().is_ok()
&& forked.parse::<Expr>().is_ok()
{
attrs.push(content.parse::<ElementAttr>()?);
} else {
children.push(content.parse::<Node>()?);
}
// 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 {
name,
attrs,
children,
})
}
}
impl ToTokens for &Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name.to_string();
@@ -288,86 +348,22 @@ impl ToTokens for &Element {
}
}
impl Parse for Element {
fn parse(s: ParseStream) -> Result<Self> {
let name = Ident::parse_any(s)?;
if !crate::util::is_valid_tag(&name.to_string()) {
return Err(Error::new(name.span(), "Not a valid Html tag"));
}
// parse the guts
let content: ParseBuffer;
syn::braced!(content in s);
let mut attrs: Vec<Attr> = vec![];
let mut children: Vec<Node> = vec![];
parse_element_content(content, &mut attrs, &mut children)?;
Ok(Self {
name,
attrs,
children,
})
}
}
// used by both vcomponet and velement to parse the contents of the elements into attras and children
fn parse_element_content(
stream: ParseBuffer,
attrs: &mut Vec<Attr>,
children: &mut Vec<Node>,
) -> Result<()> {
'parsing: loop {
// consume comma if it exists
// we don't actually care if there *are* commas after elements/text
if stream.peek(Token![,]) {
let _ = stream.parse::<Token![,]>();
}
// [1] Break if empty
if stream.is_empty() {
break 'parsing;
}
// [2] Try to consume an attr
let fork = stream.fork();
if let Ok(attr) = fork.parse::<Attr>() {
// make sure to advance or your computer will become a space heater :)
stream.advance_to(&fork);
attrs.push(attr);
continue 'parsing;
}
// [3] Try to consume a child node
let fork = stream.fork();
if let Ok(node) = fork.parse::<Node>() {
// make sure to advance or your computer will become a space heater :)
stream.advance_to(&fork);
children.push(node);
continue 'parsing;
}
// todo: pass a descriptive error onto the offending tokens
panic!("Entry is not an attr or element\n {}", stream)
}
Ok(())
}
fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
let content;
syn::braced!(content in stream);
content.parse()
}
/// =======================================
/// Parse a VElement's Attributes
/// =======================================
struct Attr {
struct ElementAttr {
name: Ident,
ty: AttrType,
}
impl Parse for Attr {
enum AttrType {
Value(LitStr),
FieldTokens(Expr),
EventTokens(Expr),
Event(ExprClosure),
}
impl Parse for ElementAttr {
fn parse(s: ParseStream) -> Result<Self> {
let mut name = Ident::parse_any(s)?;
let name_str = name.to_string();
@@ -389,32 +385,39 @@ impl Parse for Attr {
content.advance_to(&fork);
AttrType::Event(event)
} else {
AttrType::Tok(content.parse()?)
AttrType::EventTokens(content.parse()?)
}
} else {
AttrType::Event(s.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
outer.parse()?
// }
let fork = s.fork();
if let Ok(rawtext) = fork.parse::<LitStr>() {
s.advance_to(&fork);
AttrType::Value(rawtext)
} else {
s.parse()?
};
AttrType::Value(lit_str)
let toks = s.parse::<Expr>()?;
AttrType::FieldTokens(toks)
}
// 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
// outer.parse()?
// // }
// } else {
// s.parse()?
// };
};
// consume comma if it exists
@@ -423,11 +426,11 @@ impl Parse for Attr {
let _ = s.parse::<Token![,]>();
}
Ok(Attr { name, ty })
Ok(ElementAttr { name, ty })
}
}
impl ToTokens for &Attr {
impl ToTokens for &ElementAttr {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = self.name.to_string();
let nameident = &self.name;
@@ -435,7 +438,12 @@ impl ToTokens for &Attr {
match &self.ty {
AttrType::Value(value) => {
tokens.append_all(quote! {
.attr(#name, #value)
.attr(#name, {
use bumpalo::core_alloc::fmt::Write;
let mut s = bumpalo::collections::String::new_in(bump);
s.write_fmt(format_args_f!(#value)).unwrap();
s.into_bump_str()
})
});
}
AttrType::Event(event) => {
@@ -443,21 +451,21 @@ impl ToTokens for &Attr {
.add_listener(dioxus::events::on::#nameident(ctx, #event))
});
}
AttrType::Tok(exp) => {
AttrType::FieldTokens(exp) => {
tokens.append_all(quote! {
.add_listener(dioxus::events::on::#nameident(ctx, #exp))
.attr(#name, #exp)
});
}
AttrType::EventTokens(event) => {
//
tokens.append_all(quote! {
.add_listener(dioxus::events::on::#nameident(ctx, #event))
})
}
}
}
}
enum AttrType {
Value(LitStr),
Event(ExprClosure),
Tok(Expr),
}
// =======================================
// Parse just plain text
// =======================================
@@ -483,3 +491,9 @@ impl ToTokens for TextNode {
});
}
}
fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
let content;
syn::braced!(content in stream);
content.parse()
}
+4 -1
View File
@@ -117,6 +117,9 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
"var",
"video",
"wbr",
// SVTG
"svg",
"path",
]
.iter()
.cloned()
@@ -132,6 +135,6 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
///
/// assert_eq!(is_valid_tag("random"), false);
/// ```
pub fn is_valid_tag(tag: &str) -> bool {
pub fn is_valid_html_tag(tag: &str) -> bool {
VALID_TAGS.contains(tag)
}