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 {
48 break;
49 }
50 }
51 mods
52 } else {
53 Vec::new()
54 };
55
56 let children = if input.peek(syn::token::Brace) {
58 let content;
59 braced!(content in input);
60 let mut kids = Vec::new();
61 while !content.is_empty() {
62 let expr: Expr = content.parse()?;
63 kids.push(expr);
64 if content.peek(Token![,]) {
65 content.parse::<Token![,]>()?;
66 } else {
67 break;
68 }
69 }
70 kids
71 } else {
72 Vec::new()
73 };
74
75 Ok(Self {
76 layout,
77 modifiers,
78 children,
79 })
80 }
81}
82
83#[proc_macro]
108#[allow(non_snake_case)]
109pub fn View(input: TokenStream) -> TokenStream {
110 let cloned = input.clone();
112 if let Ok(m) = syn::parse::<ViewMacro>(cloned) {
113 return expand_view(m).into();
114 }
115
116 if let Ok(expr) = syn::parse::<Expr>(input.clone()) {
118 return quote!(#expr).into();
119 }
120
121 quote!({}).into()
122}
123
124fn expand_view(m: ViewMacro) -> proc_macro2::TokenStream {
125 let ViewMacro {
126 layout,
127 modifiers,
128 children,
129 } = m;
130
131 if children.is_empty() && modifiers.is_empty() {
132 return quote!({});
133 }
134
135 let layout_name = layout.as_ref().map(|i| i.to_string()).unwrap_or_default();
136
137 let mod_calls = modifiers.iter().map(|(name, value)| {
138 if let Some(val) = value {
139 quote!(.#name(#val))
140 } else {
141 quote!(.#name())
142 }
143 });
144
145 if children.is_empty() {
146 if modifiers.is_empty() {
148 quote!({})
149 } else {
150 quote! {
151 repose_ui::#layout_name(repose_core::Modifier::new() #(#mod_calls)*)
152 }
153 }
154 } else if layout.is_some() {
155 let child_exprs = &children;
157 quote! {
158 repose_ui::#layout_name(repose_core::Modifier::new() #(#mod_calls)*)
159 .child((#(#child_exprs,)*))
160 }
161 } else {
162 let child_exprs = &children;
164 quote! {
165 repose_ui::Column(repose_core::Modifier::new()).child((#(#child_exprs,)*))
166 }
167 }
168}