1use proc_macro2::TokenStream;
7use quote::quote;
8use sycamore_view_parser::ir::{DynNode, Node, Prop, PropType, Root, TagIdent, TagNode, TextNode};
9use syn::{Expr, Pat};
10
11pub struct Codegen {
12 }
14
15impl Codegen {
16 pub fn root(&self, root: &Root) -> TokenStream {
17 match &root.0[..] {
18 [] => quote! {
19 ::sycamore::rt::View::new()
20 },
21 [node] => self.node(node),
22 nodes => {
23 let nodes = nodes.iter().map(|node| self.node(node));
24 quote! {
25 ::std::convert::Into::<::sycamore::rt::View>::into(::std::vec![#(#nodes),*])
26 }
27 }
28 }
29 }
30
31 pub fn node(&self, node: &Node) -> TokenStream {
33 match node {
34 Node::Tag(tag) => {
35 if is_component(&tag.ident) {
36 self.component(tag)
37 } else {
38 self.element(tag)
39 }
40 }
41 Node::Text(TextNode { value }) => quote! {
42 ::std::convert::Into::<::sycamore::rt::View>::into(#value)
43 },
44 Node::Dyn(DynNode { value }) => {
45 let is_dynamic = is_dyn(value);
46 if is_dynamic {
47 quote! {
48 ::sycamore::rt::View::from_dynamic(
49 move || ::std::convert::Into::<::sycamore::rt::View>::into(#value)
50 )
51 }
52 } else {
53 quote! {
54 ::std::convert::Into::<::sycamore::rt::View>::into(#value)
55 }
56 }
57 }
58 }
59 }
60
61 pub fn element(&self, element: &TagNode) -> TokenStream {
62 let TagNode {
63 ident,
64 props,
65 children,
66 } = element;
67
68 let attributes = props.iter().map(|attr| self.attribute(attr));
69
70 let children = children
71 .0
72 .iter()
73 .map(|child| self.node(child))
74 .collect::<Vec<_>>();
75
76 match ident {
77 TagIdent::Path(tag) => {
78 assert!(tag.get_ident().is_some(), "elements must be an ident");
79 quote! {
80 ::sycamore::rt::View::from(
81 ::sycamore::rt::tags::#tag().children(::std::vec![#(#children),*])#(#attributes)*
82 )
83 }
84 }
85 TagIdent::Hyphenated(tag) => quote! {
86 ::sycamore::rt::View::from(
87 ::sycamore::rt::custom_element(#tag).children(::std::vec![#(#children),*])#(#attributes)*
88 )
89 },
90 }
91 }
92
93 pub fn attribute(&self, attr: &Prop) -> TokenStream {
94 let value = &attr.value;
95 let is_dynamic = is_dyn(value);
96 let dyn_value = if is_dynamic {
97 quote! { move || #value }
98 } else {
99 quote! { #value }
100 };
101 match &attr.ty {
102 PropType::Plain { ident } => {
103 quote! { .#ident(#dyn_value) }
104 }
105 PropType::PlainHyphenated { ident } => {
106 quote! { .attr(#ident, #dyn_value) }
107 }
108 PropType::PlainQuoted { ident } => {
109 quote! { .attr(#ident, #dyn_value) }
110 }
111 PropType::Directive { dir, ident } => match dir.to_string().as_str() {
112 "on" => quote! { .on(::sycamore::rt::events::#ident, #value) },
113 "prop" => {
114 let ident = ident.to_string();
115 quote! { .prop(#ident, #dyn_value) }
116 }
117 "bind" => quote! { .bind(::sycamore::rt::bind::#ident, #value) },
118 _ => syn::Error::new(dir.span(), format!("unknown directive `{dir}`"))
119 .to_compile_error(),
120 },
121 PropType::Ref => quote! { .r#ref(#value) },
122 PropType::Spread => quote! { .spread(#value) },
123 }
124 }
125
126 pub fn component(
127 &self,
128 TagNode {
129 ident,
130 props,
131 children,
132 }: &TagNode,
133 ) -> TokenStream {
134 let ident = match ident {
135 TagIdent::Path(path) => path,
136 TagIdent::Hyphenated(_) => unreachable!("hyphenated tags are not components"),
137 };
138
139 let plain = props
140 .iter()
141 .filter_map(|prop| match &prop.ty {
142 PropType::Plain { ident } => Some((ident, prop.value.clone())),
143 _ => None,
144 })
145 .collect::<Vec<_>>();
146 let plain_names = plain.iter().map(|(ident, _)| ident);
147 let plain_values = plain.iter().map(|(_, value)| value);
148
149 let other_props = props
150 .iter()
151 .filter(|prop| !matches!(&prop.ty, PropType::Plain { .. }))
152 .collect::<Vec<_>>();
153 let other_attributes = other_props.iter().map(|prop| self.attribute(prop));
154
155 let children_quoted = if children.0.is_empty() {
156 quote! {}
157 } else {
158 let codegen = Codegen {};
159 let children = codegen.root(children);
160 quote! {
161 .children(
162 ::sycamore::rt::Children::new(move || {
163 #children
164 })
165 )
166 }
167 };
168 quote! {{
169 let __component = &#ident; ::sycamore::rt::component_scope(move || ::sycamore::rt::Component::create(
171 __component,
172 ::sycamore::rt::element_like_component_builder(__component)
173 #(.#plain_names(#plain_values))*
174 #(#other_attributes)*
175 #children_quoted
176 .build()
177 ))
178 }}
179 }
180}
181
182fn is_component(ident: &TagIdent) -> bool {
183 match ident {
184 TagIdent::Path(path) => {
185 path.get_ident().is_none()
186 || path
187 .get_ident()
188 .unwrap()
189 .to_string()
190 .chars()
191 .next()
192 .unwrap()
193 .is_ascii_uppercase()
194 }
195 TagIdent::Hyphenated(_) => false,
197 }
198}
199
200fn is_dyn(ex: &Expr) -> bool {
201 match ex {
202 Expr::Lit(_) | Expr::Closure(_) | Expr::Path(_) | Expr::Field(_) => false,
203
204 Expr::Paren(p) => is_dyn(&p.expr),
205 Expr::Group(g) => is_dyn(&g.expr),
206 Expr::Tuple(t) => t.elems.iter().any(is_dyn),
207 Expr::Array(a) => a.elems.iter().any(is_dyn),
208 Expr::Repeat(r) => is_dyn(&r.expr) || is_dyn(&r.len),
209 Expr::Struct(s) => s.fields.iter().any(|fv: &syn::FieldValue| is_dyn(&fv.expr)),
210
211 Expr::Cast(c) => is_dyn(&c.expr),
212 Expr::Macro(m) => is_dyn_macro(&m.mac),
213 Expr::Block(b) => is_dyn_block(&b.block),
214 Expr::Const(_const_block) => false,
215
216 Expr::Loop(l) => is_dyn_block(&l.body),
217 Expr::While(w) => is_dyn(&w.cond) || is_dyn_block(&w.body),
218 Expr::ForLoop(f) => is_dyn_pattern(&f.pat) || is_dyn(&f.expr) || is_dyn_block(&f.body),
219 Expr::Break(_) | Expr::Continue(_) => false,
220
221 Expr::Let(e) => is_dyn_pattern(&e.pat) || is_dyn(&e.expr),
222
223 Expr::Match(m) => {
224 is_dyn(&m.expr)
225 || m.arms.iter().any(|a: &syn::Arm| {
226 is_dyn_pattern(&a.pat)
227 || a.guard.as_ref().is_some_and(|(_, g_expr)| is_dyn(g_expr))
228 || is_dyn(&a.body)
229 })
230 }
231
232 Expr::If(i) => {
233 is_dyn(&i.cond)
234 || is_dyn_block(&i.then_branch)
235 || i.else_branch.as_ref().is_some_and(|(_, e)| is_dyn(e))
236 }
237
238 Expr::Unary(u) => is_dyn(&u.expr),
239 Expr::Binary(b) => is_dyn(&b.left) || is_dyn(&b.right),
240 Expr::Index(i) => is_dyn(&i.expr) || is_dyn(&i.index),
241 Expr::Range(r) => {
242 r.start.as_deref().is_some_and(is_dyn) || r.end.as_deref().is_some_and(is_dyn)
243 }
244
245 _ => true,
246 }
247}
248
249fn is_dyn_pattern(pat: &Pat) -> bool {
250 match pat {
251 Pat::Wild(_) | Pat::Lit(_) | Pat::Path(_) | Pat::Rest(_) | Pat::Type(_) | Pat::Const(_) => {
252 false
253 }
254
255 Pat::Paren(p) => is_dyn_pattern(&p.pat),
256 Pat::Or(o) => o.cases.iter().any(is_dyn_pattern),
257 Pat::Tuple(t) => t.elems.iter().any(is_dyn_pattern),
258 Pat::TupleStruct(s) => s.elems.iter().any(is_dyn_pattern),
259 Pat::Slice(s) => s.elems.iter().any(is_dyn_pattern),
260 Pat::Range(r) => {
261 r.start.as_deref().is_some_and(is_dyn) || r.end.as_deref().is_some_and(is_dyn)
262 }
263
264 Pat::Reference(r) => r.mutability.is_some(),
265 Pat::Ident(id) => {
266 (id.by_ref.is_some() && id.mutability.is_some())
267 || id
268 .subpat
269 .as_ref()
270 .is_some_and(|(_, pat)| is_dyn_pattern(pat))
271 }
272
273 Pat::Struct(s) => s
274 .fields
275 .iter()
276 .any(|fp: &syn::FieldPat| is_dyn_pattern(&fp.pat)),
277
278 _ => true,
280 }
281}
282
283fn is_dyn_macro(m: &syn::Macro) -> bool {
284 !m.path
287 .get_ident()
288 .is_some_and(|ident| "view" == &ident.to_string())
289}
290
291fn is_dyn_block(block: &syn::Block) -> bool {
292 block.stmts.iter().any(|s: &syn::Stmt| match s {
293 syn::Stmt::Expr(ex, _) => is_dyn(ex),
294 syn::Stmt::Macro(m) => is_dyn_macro(&m.mac),
295 syn::Stmt::Local(loc) => {
296 is_dyn_pattern(&loc.pat)
297 || loc.init.as_ref().is_some_and(|i| {
298 is_dyn(&i.expr) || i.diverge.as_ref().is_some_and(|(_, ex)| is_dyn(ex))
299 })
300 }
301 syn::Stmt::Item(_) => false,
302 })
303}