Skip to main content

bern_syntax/
ui.rs

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        // Children are separated by line breaks; a stray comma is tolerated
18        // only to give a precise message.
19        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
144/// Number of path segments when the input starts with `a.b(…)`.
145fn 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
167/// `receiver.member.method(positional args)`.
168fn 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}