1use crate::ast::*;
2use crate::expr::parse_expr;
3use syn::parse::{Parse, ParseStream};
4use syn::{Ident, Token, braced, parenthesized, token};
5
6impl Parse for UiBody {
7 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
8 let nodes = parse_nodes(input)?;
9 Ok(UiBody { nodes })
10 }
11}
12
13fn parse_nodes(input: ParseStream<'_>) -> syn::Result<Vec<Node>> {
14 let mut nodes = Vec::new();
15 while !input.is_empty() {
16 nodes.push(parse_node(input)?);
17 if input.peek(Token![,]) {
20 return Err(input.error("separate child nodes with line breaks, not commas"));
21 }
22 }
23 Ok(nodes)
24}
25
26fn parse_block(input: ParseStream<'_>) -> syn::Result<Vec<Node>> {
27 let content;
28 braced!(content in input);
29 parse_nodes(&content)
30}
31
32fn parse_node(input: ParseStream<'_>) -> syn::Result<Node> {
33 if input.peek(Token![if]) {
34 return parse_if(input);
35 }
36 if input.peek(Token![match]) {
37 return parse_match(input).map(Node::Match);
38 }
39 if input.peek(Token![for]) {
40 return Err(input.error(
41 "`for` loops are not part of the grammar; use `List(items: …, key: …) |item| { … }` \
42 or `ForEach(items: …, key: …) |item| { … }`",
43 ));
44 }
45 if input.peek(Token![let]) {
46 return Err(input.error("`let` statements are not allowed inside `ui!`"));
47 }
48 if input.peek(Ident) {
49 let fork = input.fork();
50 let name: Ident = fork.parse()?;
51 let first = name.to_string().chars().next().unwrap_or('a');
52 if first.is_ascii_uppercase() {
53 return parse_element(input).map(Node::Element);
54 }
55 return parse_component_call(input).map(Node::Component);
56 }
57 Err(input.error("expected an element, a component call, `if` or `match`"))
58}
59
60fn parse_props(input: ParseStream<'_>) -> syn::Result<Vec<Prop>> {
61 let content;
62 parenthesized!(content in input);
63 let mut props = Vec::new();
64 while !content.is_empty() {
65 let attrs = content.call(syn::Attribute::parse_outer)?;
66 let name: Ident = content.parse()?;
67 if !content.peek(Token![:]) {
68 return Err(syn::Error::new(
69 name.span(),
70 format!("expected `{name}: value`; properties are always named"),
71 ));
72 }
73 content.parse::<Token![:]>()?;
74 let value = match call_segments(&content) {
75 Some(segments) if name == "query" => {
76 if segments < 2 {
77 return Err(content.error(
78 "a query is called on a typed client, for example \
79 `query: app.search(draft.text)`",
80 ));
81 }
82 Expr::Query(parse_call(&content)?)
83 }
84 _ => parse_expr(&content, true)?,
85 };
86 if props.iter().any(|prop: &Prop| prop.name == name) {
87 return Err(syn::Error::new(
88 name.span(),
89 format!("property `{name}` is given twice"),
90 ));
91 }
92 props.push(Prop { attrs, name, value });
93 if content.is_empty() {
94 break;
95 }
96 content.parse::<Token![,]>()?;
97 }
98 Ok(props)
99}
100
101fn parse_element(input: ParseStream<'_>) -> syn::Result<Element> {
102 let name: Ident = input.parse()?;
103 let span = name.span();
104 let props = if input.peek(token::Paren) {
105 parse_props(input)?
106 } else {
107 Vec::new()
108 };
109 let template = if input.peek(Token![|]) {
110 input.parse::<Token![|]>()?;
111 let ident: Ident = input.parse()?;
112 input.parse::<Token![|]>()?;
113 Some(ident)
114 } else {
115 None
116 };
117 let children = if input.peek(token::Brace) {
118 Some(parse_block(input)?)
119 } else {
120 None
121 };
122 if template.is_some() && children.is_none() {
123 return Err(syn::Error::new(
124 span,
125 "a row template `|item|` must be followed by `{ … }`",
126 ));
127 }
128 let action = if input.peek(Token![=>]) {
129 input.parse::<Token![=>]>()?;
130 Some(parse_action(input)?)
131 } else {
132 None
133 };
134 Ok(Element {
135 name,
136 props,
137 template,
138 children,
139 action,
140 span,
141 })
142}
143
144fn call_segments(input: ParseStream<'_>) -> Option<usize> {
146 let fork = input.fork();
147 fork.parse::<Ident>().ok()?;
148 let mut segments = 1;
149 while fork.peek(Token![.]) {
150 fork.parse::<Token![.]>().ok()?;
151 fork.parse::<Ident>().ok()?;
152 segments += 1;
153 }
154 fork.peek(token::Paren).then_some(segments)
155}
156
157fn parse_action(input: ParseStream<'_>) -> syn::Result<Action> {
158 if input.peek(Token![|]) || input.peek(Token![move]) || input.peek(token::Brace) {
159 return Err(input.error(
160 "arbitrary callbacks are not allowed; bind a registered command such as \
161 `=> app.save()`",
162 ));
163 }
164 parse_call(input)
165}
166
167fn parse_call(input: ParseStream<'_>) -> syn::Result<Action> {
169 let span = input.span();
170 let mut segments: Vec<Ident> = vec![input.parse()?];
171 while input.peek(Token![.]) {
172 input.parse::<Token![.]>()?;
173 segments.push(input.parse()?);
174 }
175 if input.peek(Token![=]) {
176 return Err(
177 input.error("direct assignment is not allowed; state changes go through commands")
178 );
179 }
180 if !input.peek(token::Paren) {
181 return Err(syn::Error::new(
182 span,
183 "an action must call a command, for example `=> app.save()`",
184 ));
185 }
186 if segments.len() < 2 {
187 return Err(syn::Error::new(
188 span,
189 "an action must call a command on a typed client, for example `=> app.save()`",
190 ));
191 }
192 let content;
193 parenthesized!(content in input);
194 let mut args = Vec::new();
195 while !content.is_empty() {
196 if content.peek(Ident) && content.peek2(Token![:]) && !content.peek2(Token![::]) {
197 return Err(content.error(
198 "command arguments are positional, in the order the command declares them",
199 ));
200 }
201 args.push(parse_expr(&content, true)?);
202 if content.is_empty() {
203 break;
204 }
205 content.parse::<Token![,]>()?;
206 }
207 let method = segments.pop().unwrap();
208 Ok(Action {
209 receiver: segments,
210 method,
211 args,
212 span,
213 })
214}
215
216fn parse_component_call(input: ParseStream<'_>) -> syn::Result<ComponentCall> {
217 let span = input.span();
218 let mut path: Vec<Ident> = vec![input.parse()?];
219 while input.peek(Token![::]) {
220 input.parse::<Token![::]>()?;
221 path.push(input.parse()?);
222 }
223 if input.peek(Token![.]) {
224 return Err(syn::Error::new(
225 span,
226 "a binding cannot be a child node; wrap it in `Text(content: …)`",
227 ));
228 }
229 if input.peek(Token![!]) {
230 return Err(syn::Error::new(
231 span,
232 "macros other than `tr!` are not allowed inside `ui!`",
233 ));
234 }
235 if !input.peek(token::Paren) {
236 return Err(syn::Error::new(
237 span,
238 "a component reference needs an argument list, for example `settings_screen()`",
239 ));
240 }
241 let args = parse_props(input)?;
242 if input.peek(token::Brace) {
243 return Err(input.error("component references do not take children in this version"));
244 }
245 if input.peek(Token![=>]) {
246 return Err(input.error("component references cannot bind actions"));
247 }
248 Ok(ComponentCall { path, args, span })
249}
250
251fn parse_if(input: ParseStream<'_>) -> syn::Result<Node> {
252 let span = input.parse::<Token![if]>()?.span;
253 if input.peek(Token![let]) {
254 input.parse::<Token![let]>()?;
255 let some: Ident = input.parse()?;
256 if some != "Some" {
257 return Err(syn::Error::new(
258 some.span(),
259 "only `if let Some(name) = value` is supported",
260 ));
261 }
262 let content;
263 parenthesized!(content in input);
264 let binding: Ident = content.parse()?;
265 if !content.is_empty() {
266 return Err(content.error("expected a single binding name"));
267 }
268 input.parse::<Token![=]>()?;
269 let value = parse_expr(input, false)?;
270 let then = parse_block(input)?;
271 let otherwise = parse_else(input)?;
272 return Ok(Node::IfLet(IfLetNode {
273 binding,
274 value,
275 then,
276 otherwise,
277 span,
278 }));
279 }
280 let mut branches = Vec::new();
281 let condition = parse_expr(input, false)?;
282 branches.push((condition, parse_block(input)?));
283 let mut otherwise = None;
284 while input.peek(Token![else]) {
285 input.parse::<Token![else]>()?;
286 if input.peek(Token![if]) {
287 input.parse::<Token![if]>()?;
288 if input.peek(Token![let]) {
289 return Err(input.error("`else if let` is not supported; nest the `if let`"));
290 }
291 let condition = parse_expr(input, false)?;
292 branches.push((condition, parse_block(input)?));
293 } else {
294 otherwise = Some(parse_block(input)?);
295 break;
296 }
297 }
298 Ok(Node::If(IfNode {
299 branches,
300 otherwise,
301 span,
302 }))
303}
304
305fn parse_else(input: ParseStream<'_>) -> syn::Result<Option<Vec<Node>>> {
306 if input.peek(Token![else]) {
307 input.parse::<Token![else]>()?;
308 if input.peek(Token![if]) {
309 return Err(input.error("`else if` after `if let` is not supported"));
310 }
311 Ok(Some(parse_block(input)?))
312 } else {
313 Ok(None)
314 }
315}
316
317fn parse_match(input: ParseStream<'_>) -> syn::Result<MatchNode> {
318 let span = input.parse::<Token![match]>()?.span;
319 let value = parse_expr(input, false)?;
320 let content;
321 braced!(content in input);
322 let mut arms = Vec::new();
323 while !content.is_empty() {
324 let attrs = content.call(syn::Attribute::parse_outer)?;
325 let arm_span = content.span();
326 let pattern = if content.peek(Token![_]) {
327 Pattern::Wildcard(content.parse::<Token![_]>()?.span)
328 } else {
329 let ty: Ident = content.parse()?;
330 content.parse::<Token![::]>()?;
331 let variant: Ident = content.parse()?;
332 let mut bindings = Vec::new();
333 if content.peek(token::Brace) {
334 let fields;
335 braced!(fields in content);
336 while !fields.is_empty() {
337 bindings.push(fields.parse()?);
338 if fields.is_empty() {
339 break;
340 }
341 fields.parse::<Token![,]>()?;
342 }
343 } else if content.peek(token::Paren) {
344 return Err(
345 content.error("use named fields in patterns: `Type::Variant { field }`")
346 );
347 }
348 Pattern::Variant {
349 ty,
350 variant,
351 bindings,
352 }
353 };
354 if content.peek(Token![if]) {
355 return Err(content.error("match guards are not supported"));
356 }
357 content.parse::<Token![=>]>()?;
358 if !content.peek(token::Brace) {
359 return Err(content.error("each match arm needs a `{ … }` block of nodes"));
360 }
361 let body = parse_block(&content)?;
362 arms.push(MatchArm {
363 attrs,
364 pattern,
365 body,
366 span: arm_span,
367 });
368 if content.peek(Token![,]) {
369 content.parse::<Token![,]>()?;
370 }
371 }
372 if arms.is_empty() {
373 return Err(syn::Error::new(span, "`match` needs at least one arm"));
374 }
375 Ok(MatchNode { value, arms, span })
376}