1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 Expr, Ident, Token, braced,
5 parse::{Parse, ParseStream},
6};
7
8struct ViewMacro {
9 layout: Option<Ident>,
10 modifiers: Vec<(Ident, Option<Expr>)>,
11 children: Vec<Expr>,
12}
13
14impl Parse for ViewMacro {
15 fn parse(input: ParseStream) -> syn::Result<Self> {
16 if input.peek(syn::token::Paren) || input.peek(syn::token::Bracket) {
18 return Err(syn::Error::new(input.span(), "unexpected delimiters"));
19 }
20
21 let layout = if input.peek(Ident)
23 && (input.peek2(syn::token::Brace) || input.peek2(syn::token::Paren))
24 {
25 let ident: Ident = input.parse()?;
26 Some(ident)
27 } else {
28 None
29 };
30
31 let modifiers = if input.peek(syn::token::Paren) {
33 let content;
34 syn::parenthesized!(content in input);
35 let mut mods = Vec::new();
36 while !content.is_empty() {
37 let name: Ident = content.parse()?;
38 let value = if content.peek(Token![:]) {
39 content.parse::<Token![:]>()?;
40 Some(content.parse::<Expr>()?)
41 } else {
42 None
43 };
44 mods.push((name, value));
45 if content.peek(Token![,]) {
46 content.parse::<Token![,]>()?;
47 } else if content.is_empty() {
48 break;
49 } else {
50 return Err(syn::Error::new(
51 content.span(),
52 "expected `,` between modifier args",
53 ));
54 }
55 }
56 mods
57 } else {
58 Vec::new()
59 };
60
61 let children = if input.peek(syn::token::Brace) {
63 let content;
64 braced!(content in input);
65 let mut kids = Vec::new();
66 while !content.is_empty() {
67 let expr: Expr = content.parse()?;
68 kids.push(expr);
69 if content.peek(Token![,]) {
70 content.parse::<Token![,]>()?;
71 } else if content.is_empty() {
72 break;
73 } else {
74 return Err(syn::Error::new(
75 content.span(),
76 "expected `,` between children",
77 ));
78 }
79 }
80 kids
81 } else {
82 Vec::new()
83 };
84
85 Ok(Self {
86 layout,
87 modifiers,
88 children,
89 })
90 }
91}
92
93#[proc_macro]
118#[allow(non_snake_case)]
119pub fn View(input: TokenStream) -> TokenStream {
120 let cloned = input.clone();
122 match syn::parse::<ViewMacro>(cloned) {
123 Ok(m) => expand_view(m).into(),
124 Err(macro_err) => {
125 if let Ok(expr) = syn::parse::<Expr>(input) {
126 return quote!(#expr).into();
127 }
128 macro_err.to_compile_error().into()
129 }
130 }
131}
132
133fn expand_view(m: ViewMacro) -> proc_macro2::TokenStream {
134 let ViewMacro {
135 layout,
136 modifiers,
137 children,
138 } = m;
139
140 let compile_err = || {
141 syn::Error::new(
142 proc_macro2::Span::call_site(),
143 "View!: expected a single expression or `Layout(modifiers) { children }`",
144 )
145 .to_compile_error()
146 };
147
148 if children.is_empty() && modifiers.is_empty() {
149 return compile_err();
150 }
151
152 let mod_calls = modifiers.iter().map(|(name, value)| {
153 if let Some(val) = value {
154 quote!(.#name(#val))
155 } else {
156 quote!(.#name())
157 }
158 });
159
160 if children.is_empty() {
161 if modifiers.is_empty() {
163 compile_err()
164 } else if let Some(layout) = layout {
165 quote! {
166 ::repose_ui::#layout(::repose_core::Modifier::new() #(#mod_calls)*)
167 }
168 } else {
169 quote! {
170 ::repose_ui::Column(::repose_core::Modifier::new() #(#mod_calls)*)
171 }
172 }
173 } else if let Some(layout) = layout {
174 let child_exprs = &children;
176 quote! {
177 ::repose_ui::#layout(::repose_core::Modifier::new() #(#mod_calls)*)
178 .child((#(#child_exprs,)*))
179 }
180 } else {
181 let child_exprs = &children;
183 let mod_tokens = if modifiers.is_empty() {
184 quote!(::repose_core::Modifier::new())
185 } else {
186 quote!(::repose_core::Modifier::new() #(#mod_calls)*)
187 };
188 quote! {
189 ::repose_ui::Column(#mod_tokens).child((#(#child_exprs,)*))
190 }
191 }
192}