Feat: parse custom rsx syntax

This commit is contained in:
Jonathan Kelley committed 2021-03-01 22:55:20 -05:00
1 parent 114a446c0a
commit da00df6688
4 files changed
+271 -172

No files matched your search

+10 -10
View File
@@ -1,23 +1,23 @@
[workspace]
members = [
# Built-in
"packages/dioxus",
"packages/core",
"packages/core-macro",
"packages/router",
"packages/ssr",
"packages/webview",
"packages/web",
"packages/livehost",
"packages/vscode-ext",
# "packages/dioxus",
# "packages/core",
# "packages/router",
# "packages/ssr",
# "packages/webview",
# "packages/web",
# "packages/livehost",
# "packages/vscode-ext",
# "packages/recoil",
# "packages/redux",
# "packages/macro",
# TODO @Jon, share the validation code
# "packages/web",
# "packages/hooks",
"packages/cli",
"examples",
# "packages/cli",
# "examples",
# "packages/html-macro",
# "packages/html-macro-2",
#
+46
View File
@@ -0,0 +1,46 @@
use dioxus_core_macro::rsx;
pub fn main() {
// render(rsx! {
// _ {}
// });
let g = String::from("asd");
let lazy = rsx! {
div {
a: "asd",
a: "asd",
a: "asd",
a: "asd",
a: "asd",
a: {rsx!{ h1 {"hello world"} }}, // include
a: {g},
b: {1 + 2},
onclick: {move |_| {
println!("hello world!")
}},
div {
a: "asd"
div {
div {
div {
}
}
}
}
h1 {
}
h2 {
"child"
}
"Childnode"
}
};
render(lazy);
}
fn render(f: impl Fn(())) {}
+117 -82
View File
@@ -1,5 +1,8 @@
/*
https://github.com/chinedufn/percy/issues/37
An example usage of rsx! would look like this:
```ignore
ctx.render(rsx!{
@@ -30,7 +33,7 @@ ctx.render(rsx!{
each element is given by tag { [Attr] }
*/
use syn::parse::ParseBuffer;
use syn::parse::{discouraged::Speculative, ParseBuffer};
use {
proc_macro::TokenStream,
@@ -50,6 +53,10 @@ pub struct RsxRender {}
impl Parse for RsxRender {
fn parse(input: ParseStream) -> Result<Self> {
// cannot accept multiple elements
// can only accept one root per component
// fragments can be used as
// _ { content }
let g: Element = input.parse()?;
Ok(Self {})
}
@@ -84,28 +91,32 @@ impl ToTokens for ToToksCtx<&Node> {
}
}
impl Node {
fn peek(s: ParseStream) -> bool {
(s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr)
}
}
// 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()?)
let fork = s.fork();
if let Ok(text) = fork.parse::<TextNode>() {
s.advance_to(&fork);
return Ok(Self::Text(text));
} else if let Ok(el) = s.parse::<Element>() {
return Ok(Self::Element(el));
} else {
Node::Text(s.parse()?)
})
panic!("Not a valid child node");
}
}
}
/// =======================================
/// Parse the VNode::Element type
/// =======================================
/// - [ ] Allow VComponent
///
///
// =======================================
// Parse the VNode::Element type
// =======================================
// - [ ] Allow VComponent
//
//
struct Element {
name: Ident,
attrs: Vec<Attr>,
@@ -114,79 +125,96 @@ struct Element {
impl ToTokens for ToToksCtx<&Element> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
// let ctx = self.ctx;
let name = &self.inner.name;
tokens.append_all(quote! {
dioxus::builder::#name(bump)
});
for attr in self.inner.attrs.iter() {
self.recurse(attr).to_tokens(tokens);
}
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);
while let Some(child) = children.next() {
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()
});
todo!()
// // let ctx = self.ctx;
// let name = &self.inner.name;
// tokens.append_all(quote! {
// dioxus::builder::#name(bump)
// });
// for attr in self.inner.attrs.iter() {
// self.recurse(attr).to_tokens(tokens);
// }
// 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);
// while let Some(child) = children.next() {
// 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> {
// steps:
// grab ident as name
// peak to the next character
// ensure it's a {
// s.parse::<Token![<]>()?;
// TODO: reject anything weird/nonstandard
// we want names *only*
let name = Ident::parse_any(s)?;
// parse the guts
let content: ParseBuffer;
// parse the guts of the div {} tag into the content buffer
syn::braced!(content in s);
// s.
// s.parse::<Token!["{"]>()?;
// s.parse()
// s.parse_terminated(parser)
// s.parse::<token::Brace>()?;
let mut attrs = vec![];
// let mut children = vec![];
let mut 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![>]>()?;
'parsing: loop {
// todo move this around into a more functional style
// [1] Break if empty
// [2] Try to consume an attr (with comma)
// [3] Try to consume a child node (with comma)
// [4] Try to consume brackets as anything thats Into<Node>
// [last] Fail if none worked
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
// [2] Try to consume an attr
let fork = content.fork();
if let Ok(attr) = fork.parse::<Attr>() {
// make sure to advance or your computer will become a spaceheater :)
content.advance_to(&fork);
attrs.push(attr);
continue 'parsing;
}
// [3] Try to consume a child node
let fork = content.fork();
if let Ok(node) = fork.parse::<Node>() {
// make sure to advance or your computer will become a spaceheater :)
content.advance_to(&fork);
children.push(node);
continue 'parsing;
}
// [4] TODO: Parsing brackets
// let fork = content.fork();
// if let Ok(el) = fork.parse() {
// children.push(el);
// continue 'parsing;
// }
// todo: pass a descriptive error onto the offending tokens
panic!("Entry is not an attr or element\n {}", content)
}
let children = MaybeExpr::Literal(Vec::new());
// // 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) {
@@ -237,7 +265,7 @@ 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![=]>()?;
s.parse::<Token![:]>()?;
// Check if this is an event handler
// If so, parse into literal tokens
@@ -271,6 +299,13 @@ impl Parse for Attr {
};
AttrType::Value(lit_str)
};
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if s.peek(Token![,]) {
let _ = s.parse::<Token![,]>();
}
Ok(Attr { name, ty })
}
}
@@ -301,12 +336,12 @@ enum AttrType {
// todo Bool(MaybeExpr<LitBool>)
}
/// =======================================
/// Parse just plain text
/// =======================================
/// - [ ] Perform formatting automatically
///
///
// =======================================
// Parse just plain text
// =======================================
// - [ ] Perform formatting automatically
//
//
struct TextNode(MaybeExpr<LitStr>);
impl Parse for TextNode {
+98 -80
View File
@@ -2,88 +2,106 @@ use dioxus_core::prelude::*;
fn main() {}
fn Example(ctx: Context, props: ()) -> DomTree {
// include as much as you might accept
struct ButtonProps<'a> {
onhover: Option<&'a dyn Fn()>,
// // A list of any attrs
// attrs: AttrList<'a>,
}
fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree {
let onfocus = move |evt: ()| log::debug!("Focused");
ctx.render(rsx! {
div {
<h1 tag="type" abc=123 class="big..."> "title1" "title2" </h1>
<h1 alsd> alkjsd </h1>
h1 { tag: "type", abc: 123, class: "big small wide short",
"title1"
"title1"
"title1"
"title"
}
h1 ("title") {
tag: "type",
abc: 123,
class: "big small wide short",
}
// <button
// class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
// onclick={move |_| set_name("jill")}
// onclick={move |_| set_name("jill")}
// >
// "Jill!"
// </button>
button {
// ..props.attrs,
class: "abc123",
style: { a: 2, b: 3, c: 4 },
onclick: move |evt| {
log::info("hello world");
},
onfocus,
onhover: props.onhover,
button { "Jill!",
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: move |_| set_name("jill"),
onclick: move |_| set_name("jill"),
}
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: move |_| set_name("jill"),
onclick: move |_| set_name("jill"),
// this is valid
"Jill!",
// this is also valid
{"Jill!"}
}
h1 { "Text", class: "inline-block py-4 px-8 mr-6 leading-none" }
// <h1 class="inline-block py-4 px-8 mr-6 leading-none">
// "Text"
// </h1>
h1 {
div {
h1 {}
h2 {}
Brick {}
p {}
p {
tag: "type",
abc: 123,
enabled: true,
class: "big small wide short",
a { "abcder" },
h2 { "whatsup", class: "abc-123" },
CustomComponent { a: 123, b: 456, key: "1" },
}
div { class: "big small wide short",
div {},
div {},
div {},
div {},
}
}
}
h2 {}
h3 {}
"abcd123"
Custom1 { a: 123 }
Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
Custom3 { a: "sometext goes here" }
Custom4 { onclick: |evt| log::info("click") }
}
})
}
// h1 {
// tag: "type", abc: 123, class: "big small wide short",
// "title1"
// "title1"
// "title1"
// "title"
// }
// h1 ("title") {
// tag: "type",
// abc: 123,
// class: "big small wide short",
// }
// // <button
// // class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
// // onclick={move |_| set_name("jill")}
// // onclick={move |_| set_name("jill")}
// // >
// // "Jill!"
// // </button>
// button { "Jill!",
// class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
// onclick: move |_| set_name("jill"),
// onclick: move |_| set_name("jill"),
// }
// button {
// class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
// onclick: move |_| set_name("jill"),
// onclick: move |_| set_name("jill"),
// // this is valid
// "Jill!",
// // this is also valid
// {"Jill!"}
// }
// h1 { "Text", class: "inline-block py-4 px-8 mr-6 leading-none" }
// // <h1 class="inline-block py-4 px-8 mr-6 leading-none">
// // "Text"
// // </h1>
// h1 {
// div {
// h1 {}
// h2 {}
// Brick {}
// p {}
// p {
// tag: "type",
// abc: 123,
// enabled: true,
// class: "big small wide short",
// a { "abcder" },
// h2 { "whatsup", class: "abc-123" },
// CustomComponent { a: 123, b: 456, key: "1" },
// }
// div { class: "big small wide short",
// div {},
// div {},
// div {},
// div {},
// }
// }
// }
// h2 {}
// h3 {}
// "abcd123"