1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use quote::quote;
6use syn::punctuated::Punctuated;
7use syn::{parse_macro_input, Ident, Token};
8
9mod parser;
10use crate::parser::Ctrl;
11use crate::parser::CtrlParam;
12use crate::parser::CtrlProperty;
13
14#[proc_macro]
94pub fn ui(input: TokenStream) -> TokenStream {
95 let ctrl = parse_macro_input!(input as Ctrl);
96 let x = quote_control(ctrl);
97
98 x.into()
99
100 }
103
104fn quote_control(ctrl: Ctrl) -> proc_macro2::TokenStream {
105 let Ctrl {
106 name: control_name,
107 params,
108 } = ctrl;
109
110 let (style, properties, attached_values, children) = decouple_params(params);
111
112 let properties_builder = get_properties_builder(control_name.clone(), properties);
113 let style_builder = get_style_builder(control_name, style);
114 let attached_values_typemap = get_attached_values_typemap(attached_values);
115 let children_source = get_children_source(children);
116
117 quote! { #properties_builder.to_view(#style_builder, ViewContext {
118 attached_values: #attached_values_typemap,
119 children: #children_source,
120 }) }
121}
122
123fn get_properties_builder(
124 struct_name: Ident,
125 properties: Vec<CtrlProperty>,
126) -> proc_macro2::TokenStream {
127 let mut method_calls = Vec::new();
128 for property in properties {
129 let name = property.name;
130 let expr = property.expr;
131 method_calls.push(quote!(.#name((#expr).into())))
132 }
133 quote!(<#struct_name>::builder()#(#method_calls)*.build())
134}
135
136fn get_style_builder(control_name: Ident, style: Option<Ctrl>) -> proc_macro2::TokenStream {
137 match style {
138 None => quote!(None),
139 Some(style) => {
140 let name = style.name;
141
142 let style_name = Ident::new(&format!("{}{}Style", name, control_name), name.span());
143 let params_name =
144 Ident::new(&format!("{}{}StyleParams", name, control_name), name.span());
145
146 let (_style, properties, _attached_values, _children) = decouple_params(style.params);
147
148 let properties_builder = get_properties_builder(params_name, properties);
149
150 quote!(Some(Box::new(<#style_name>::new(#properties_builder))))
151 }
152 }
153}
154
155fn get_attached_values_typemap(attached_values: Vec<CtrlProperty>) -> proc_macro2::TokenStream {
156 let mut insert_statements = Vec::new();
157 for attached_value in attached_values {
158 let name = attached_value.name;
159 let expr = attached_value.expr;
160 insert_statements.push(quote!(map.insert::<#name>((#expr).into());))
161 }
162 quote!({ let mut map = TypeMap::new(); #(#insert_statements)* map })
163}
164
165fn get_children_source(children: Vec<CtrlParam>) -> proc_macro2::TokenStream {
166 let mut sources = Vec::new();
167
168 for child in children {
169 if let CtrlParam::ChildCtrl(static_child) = child {
170 sources.push(quote_control(static_child));
171 } else if let CtrlParam::ChildExpr(expression) = child {
172 sources.push(quote!(#expression));
173 }
174 }
175
176 quote!({ Children::from(vec![#((#sources).into()),*]) })
177}
178
179fn decouple_params(
180 params: Punctuated<CtrlParam, Token![,]>,
181) -> (
182 Option<Ctrl>,
183 Vec<CtrlProperty>,
184 Vec<CtrlProperty>,
185 Vec<CtrlParam>,
186) {
187 let mut style = None;
188 let mut properties = Vec::new();
189 let mut attached_values = Vec::new();
190 let mut children = Vec::new();
191
192 for el in params.into_pairs() {
193 let el = el.into_value();
194
195 if let CtrlParam::Style(s) = el {
196 style = Some(s);
197 } else if let CtrlParam::Property(property) = el {
198 if let Some(first_char) = property.name.to_string().chars().next() {
199 if first_char.is_uppercase() {
200 attached_values.push(property);
201 } else {
202 properties.push(property);
203 }
204 }
205 } else if let CtrlParam::ChildCtrl(control) = el {
206 children.push(CtrlParam::ChildCtrl(control))
207 } else if let CtrlParam::ChildExpr(expression) = el {
208 children.push(CtrlParam::ChildExpr(expression))
209 }
210 }
211
212 (style, properties, attached_values, children)
213}