1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 braced,
5 parse::{Parse, ParseStream},
6 parse_macro_input, Expr, Ident, Token,
7};
8
9struct ViewMacro {
10 layout: Option<Ident>,
11 modifiers: Vec<(Ident, Option<Expr>)>,
12 children: Vec<Expr>,
13}
14
15impl Parse for ViewMacro {
16 fn parse(input: ParseStream) -> syn::Result<Self> {
17 if input.peek(syn::token::Paren) || input.peek(syn::token::Bracket) {
19 return Err(syn::Error::new(input.span(), "unexpected delimiters"));
20 }
21
22 let layout = if input.peek(Ident)
24 && (input.peek2(syn::token::Brace) || input.peek2(syn::token::Paren))
25 {
26 let ident: Ident = input.parse()?;
27 Some(ident)
28 } else {
29 None
30 };
31
32 let modifiers = if input.peek(syn::token::Paren) {
34 let content;
35 syn::parenthesized!(content in input);
36 let mut mods = Vec::new();
37 while !content.is_empty() {
38 let name: Ident = content.parse()?;
39 let value = if content.peek(Token![:]) {
40 content.parse::<Token![:]>()?;
41 Some(content.parse::<Expr>()?)
42 } else {
43 None
44 };
45 mods.push((name, value));
46 if content.peek(Token![,]) {
47 content.parse::<Token![,]>()?;
48 } else {
49 break;
50 }
51 }
52 mods
53 } else {
54 Vec::new()
55 };
56
57 let children = if input.peek(syn::token::Brace) {
59 let content;
60 braced!(content in input);
61 let mut kids = Vec::new();
62 while !content.is_empty() {
63 let expr: Expr = content.parse()?;
64 kids.push(expr);
65 if content.peek(Token![,]) {
66 content.parse::<Token![,]>()?;
67 } else {
68 break;
69 }
70 }
71 kids
72 } else {
73 Vec::new()
74 };
75
76 Ok(Self {
77 layout,
78 modifiers,
79 children,
80 })
81 }
82}
83
84#[proc_macro]
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
136 .as_ref()
137 .map(|i| i.to_string())
138 .unwrap_or_default();
139
140 let mod_calls = modifiers.iter().map(|(name, value)| {
141 if let Some(val) = value {
142 quote!(.#name(#val))
143 } else {
144 quote!(.#name())
145 }
146 });
147
148 if children.is_empty() {
149 if modifiers.is_empty() {
151 quote!({})
152 } else {
153 quote! {
154 repose_ui::#layout_name(repose_core::Modifier::new() #(#mod_calls)*)
155 }
156 }
157 } else if layout.is_some() {
158 let child_exprs = &children;
160 quote! {
161 repose_ui::#layout_name(repose_core::Modifier::new() #(#mod_calls)*)
162 .child((#(#child_exprs,)*))
163 }
164 } else {
165 let child_exprs = &children;
167 quote! {
168 repose_ui::Column(repose_core::Modifier::new()).child((#(#child_exprs,)*))
169 }
170 }
171}