Feat: parse custom rsx syntax
This commit is contained in:
1 parent
114a446c0a
commit
da00df6688
4 files changed
+271
-172
No files matched your search
+10
-10
@@ -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",
|
||||
#
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
Reference in new issue
Block a user