Skip to content

Commit

Permalink
Parse new view syntax (#460)
Browse files Browse the repository at this point in the history
* Add ui test for missing `()` and `{}` after element

* Parse new component syntax

* Parse new component syntax

* Fix ssr benchmark compiler error
  • Loading branch information
lukechu10 authored Jul 26, 2022
1 parent ba0efba commit 49d30f1
Show file tree
Hide file tree
Showing 27 changed files with 322 additions and 322 deletions.
2 changes: 1 addition & 1 deletion examples/components/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ fn App<G: Html>(cx: Scope) -> View<G> {
}

fn main() {
sycamore::render(|cx| view! { cx, App() });
sycamore::render(|cx| view! { cx, App {} });
}
2 changes: 1 addition & 1 deletion examples/context/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ fn App<G: Html>(cx: Scope) -> View<G> {
}

fn main() {
sycamore::render(|cx| view! { cx, App() });
sycamore::render(|cx| view! { cx, App {} });
}
2 changes: 1 addition & 1 deletion examples/hello-world/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ fn App<G: Html>(cx: Scope) -> View<G> {

fn main() {
sycamore::render(|cx| {
view! { cx, App() }
view! { cx, App {} }
});
}
3 changes: 1 addition & 2 deletions examples/http-request/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ fn App<G: Html>(cx: Scope) -> View<G> {
view! { cx,
div {
p { "Page Visit Counter" }
Suspense {
fallback: view! { cx, "Loading..." },
Suspense(fallback=view! { cx, "Loading..." }) {
VisitsCount {}
}
}
Expand Down
10 changes: 5 additions & 5 deletions examples/iteration/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@ fn App<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "The famous cats of YouTube" }
ul {
Indexed {
iterable: items,
view: |cx, Cat { id, name }| view! { cx,
Indexed(
iterable=items,
view=|cx, Cat { id, name }| view! { cx,
li {
a(href=format!("https://www.youtube.com/watch?v={id}")) {
(name)
}
}
}
}
)
}
}
}

fn main() {
sycamore::render(|cx| view! { cx, App() });
sycamore::render(|cx| view! { cx, App {} });
}
22 changes: 11 additions & 11 deletions examples/js-framework-benchmark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,21 @@ fn App<G: Html>(cx: Scope) -> View<G> {
div(class="col-md-6") { h1 { "Sycamore Keyed" } }
div(class="col-md-6") {
div(class="row") {
Button { id: "run", text: "Create 1,000 rows", callback: Box::new(run) }
Button { id: "runlots", text: "Create 10,000 rows", callback: Box::new(runlots) }
Button { id: "add", text: "Append 1,000 rows", callback: Box::new(add) }
Button { id: "update", text: "Update every 10th row", callback: Box::new(update) }
Button { id: "clear", text: "Clear", callback: Box::new(clear) }
Button { id: "swaprows", text: "Swap Rows", callback: Box::new(swaprows) }
Button(id="run", text="Create 1,000 rows", callback=Box::new(run))
Button(id="runlots", text="Create 10,000 rows", callback=Box::new(runlots))
Button(id="add", text="Append 1,000 rows", callback=Box::new(add))
Button(id="update", text="Update every 10th row", callback=Box::new(update))
Button(id="clear", text="Clear", callback=Box::new(clear))
Button(id="swaprows", text="Swap Rows", callback=Box::new(swaprows))
}
}
}
}
table(class="table table-hover table-striped test-data") {
tbody {
Keyed {
iterable: data,
view: move |cx, row| {
Keyed(
iterable=data,
view=move |cx, row| {
let is_selected = create_selector(cx, move || *selected.get() == Some(row.id));
let handle_click = move |_| selected.set(Some(row.id));
view! { cx,
Expand All @@ -187,8 +187,8 @@ fn App<G: Html>(cx: Scope) -> View<G> {
}
}
},
key: |row| row.id
}
key=|row| row.id
)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/ssr/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ fn App<G: Html>(cx: Scope) -> View<G> {
}

fn main() {
let s = sycamore::render_to_string(|cx| view! { cx, App() });
let s = sycamore::render_to_string(|cx| view! { cx, App {} });
println!("{}", s);
}
2 changes: 1 addition & 1 deletion examples/svg/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ fn App<G: Html>(cx: Scope) -> View<G> {

fn main() {
sycamore::render(|cx| {
view! { cx, App() }
view! { cx, App {} }
});
}
12 changes: 6 additions & 6 deletions examples/todomvc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ fn main() {

sycamore::render(|cx| {
view! { cx,
App()
App {}
}
});
}
Expand Down Expand Up @@ -370,13 +370,13 @@ pub fn List<G: Html>(cx: Scope) -> View<G> {
label(for="toggle-all")

ul(class="todo-list") {
Keyed {
iterable: filtered_todos,
view: |cx, todo| view! { cx,
Keyed(
iterable=filtered_todos,
view=|cx, todo| view! { cx,
Item(todo)
},
key: |todo| todo.get().id,
}
key=|todo| todo.get().id,
)
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions examples/transitions/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ fn App<G: Html>(cx: Scope) -> View<G> {
button(on:click=move |_| update(Tab::One)) { "One" }
button(on:click=move |_| update(Tab::Two)) { "Two" }
button(on:click=move |_| update(Tab::Three)) { "Three" }
Suspense {
fallback: view! { cx, p { "Loading..." } },
Suspense(fallback=view! { cx, p { "Loading..." } }) {
({
let tab = *tab.get();
view! { cx, Child(tab) }
Expand Down
56 changes: 31 additions & 25 deletions packages/sycamore-macro/src/view/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,44 +458,50 @@ impl Codegen {
pub fn component(&self, comp: &Component) -> TokenStream {
let cx = &self.cx;
match comp {
Component::FnLike(comp) => {
let FnLikeComponent { ident, args } = comp;
Component::Legacy(comp) => {
let LegacyComponent { ident, args } = comp;
quote! { ::sycamore::component::component_scope(move || #ident(#cx, #args)) }
}
Component::ElementLike(comp) => {
let ElementLikeComponent {
Component::New(comp) => {
let NewComponent {
ident,
props,
children,
..
} = comp;
if props.is_empty() && children.is_none() {
if props.is_empty()
&& (children.is_none() || children.as_ref().unwrap().0.is_empty())
{
quote! {
::sycamore::component::component_scope(move || #ident(#cx))
}
} else {
let mut props_quoted = quote! {
::sycamore::component::element_like_component_builder(__component)
};
for (field, expr) in props {
props_quoted.extend(quote! { .#field(#expr) });
}
if let Some(children) = children {
let view_root = self.view_root(children);
props_quoted.extend(quote! {
.children(
::sycamore::component::Children::new(#cx, move |#cx| {
#[allow(unused_variables)]
let #cx: ::sycamore::reactive::BoundedScope = #cx;
#view_root
})
)
});
}
props_quoted.extend(quote! { .build() });
let name = props.iter().map(|x| &x.name);
let value = props.iter().map(|x| &x.value);
let children_quoted = children
.as_ref()
.map(|children| {
let children = self.view_root(children);
quote! {
.children(
::sycamore::component::Children::new(#cx, move |#cx| {
#[allow(unused_variables)]
let #cx: ::sycamore::reactive::BoundedScope = #cx;
#children
})
)
}
})
.unwrap_or_default();
quote! {{
let __component = &#ident; // We do this to make sure the compiler can infer the value for `<G>`.
::sycamore::component::component_scope(move || __component(#cx, #props_quoted))
::sycamore::component::component_scope(move || __component(
#cx,
::sycamore::component::element_like_component_builder(__component)
#(.#name(#value))*
#children_quoted
.build()
))
}}
}
}
Expand Down
18 changes: 12 additions & 6 deletions packages/sycamore-macro/src/view/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,22 +122,28 @@ pub fn is_bool_attr(name: &str) -> bool {
}

pub enum Component {
FnLike(FnLikeComponent),
ElementLike(ElementLikeComponent),
Legacy(LegacyComponent),
New(NewComponent),
}

pub struct FnLikeComponent {
pub struct LegacyComponent {
pub ident: Path,
pub args: Punctuated<Expr, Token![,]>,
}

pub struct ElementLikeComponent {
pub struct NewComponent {
pub ident: Path,
pub brace: Brace,
pub props: Vec<(Ident, Expr)>,
pub props: Punctuated<ComponentProp, Token![,]>,
pub brace: Option<Brace>,
pub children: Option<ViewRoot>,
}

pub struct ComponentProp {
pub name: Ident,
pub eq: Token![=],
pub value: Expr,
}

pub struct Text {
pub value: LitStr,
}
Expand Down
88 changes: 37 additions & 51 deletions packages/sycamore-macro/src/view/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
use std::fmt;

use syn::ext::IdentExt;
use syn::parse::discouraged::Speculative;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::{Brace, Paren};
use syn::{braced, parenthesized, token, Expr, FieldValue, Ident, LitStr, Result, Token};
use syn::{braced, parenthesized, token, Expr, Ident, LitStr, Result, Token};

use super::ir::*;

Expand Down Expand Up @@ -220,59 +219,46 @@ impl Parse for AttributeType {
impl Parse for Component {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse()?;
let content;

let mut props = Punctuated::new();
let mut brace = None;
let mut children = None;

if input.peek(Paren) {
// Parse fn-like component.
// Parse props.
let content;
parenthesized!(content in input);
let args = content.parse_terminated(Expr::parse)?;
Ok(Self::FnLike(FnLikeComponent { ident, args }))
} else if input.peek(Brace) {
// Parse element link component.
let brace = braced!(content in input);
let mut props = Punctuated::<FieldValue, Token![,]>::new();
while !content.is_empty() {
let fork = content.fork();
if let Ok(value) = fork.parse() {
if fork.peek(Brace) || ViewNode::peek_type(&fork).is_some() {
break;
}
content.advance_to(&fork);

props.push_value(value);
if content.is_empty() {
break;
}
let punct = content.parse()?;
props.push_punct(punct);
} else {
break;
}
}
let children = if content.peek(Brace) {
// Parse view fragment as children
let children;
braced!(children in content);
Some(ViewRoot::parse(&children)?)
} else if ViewNode::peek_type(&content).is_some() {
Some(ViewRoot(vec![ViewNode::parse(&content)?]))
// Check if using legacy component syntax (not using `Prop` derive).
if !content.peek2(Token![=]) {
let args = content.parse_terminated(Expr::parse)?;
return Ok(Self::Legacy(LegacyComponent { ident, args }));
} else {
None
};
Ok(Self::ElementLike(ElementLikeComponent {
ident,
brace,
props: props
.into_iter()
.map(|x| match x.member {
syn::Member::Named(named) => (named, x.expr),
syn::Member::Unnamed(_) => todo!("implement error handling"),
})
.collect(),
children,
}))
} else {
Err(input.error("expected either `(` or `{`"))
props = content.parse_terminated(ComponentProp::parse)?;
}
}
if input.peek(Brace) {
// Parse children.
let content;
brace = Some(braced!(content in input));
children = Some(content.parse()?);
}

Ok(Self::New(NewComponent {
ident,
props,
brace,
children,
}))
}
}

impl Parse for ComponentProp {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
name: input.parse()?,
eq: input.parse()?,
value: input.parse()?,
})
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/sycamore-macro/tests/view/component-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ fn compile_fail<G: Html>() {

let _: View<G> = view! { cx, PropComponent() };
let _: View<G> = view! { cx, PropComponent {} };
let _: View<G> = view! { cx, PropComponent { prop: 123 } };
let _: View<G> = view! { cx, PropComponent(prop=123) };
let _: View<G> = view! { cx, PropComponent { prop: "123" } }; // Legacy syntax.
});
}

Expand Down
Loading

0 comments on commit 49d30f1

Please sign in to comment.