Skip to main content

i_slint_compiler/parser/
expressions.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use super::document::parse_qualified_name;
5use super::prelude::*;
6
7#[cfg_attr(test, parser_test)]
8/// ```test,Expression
9/// something
10/// "something"
11/// 0.3
12/// 42
13/// 42px
14/// #aabbcc
15/// (something)
16/// (something).something
17/// @image-url("something")
18/// @image_url("something")
19/// some_id.some_property
20/// function_call()
21/// function_call(hello, world)
22/// cond ? first : second
23/// call_cond() ? first : second
24/// (nested()) ? (ok) : (other.ko)
25/// 4 + 4
26/// 4 + 8 * 7 / 5 + 3 - 7 - 7 * 8
27/// -0.3px + 0.3px - 3.pt+3pt
28/// aa == cc && bb && (xxx || fff) && 3 + aaa == bbb
29/// [array]
30/// array[index]
31/// {object:42}
32/// "foo".bar.something().something.xx({a: 1.foo}.a)
33/// ```
34pub fn parse_expression(p: &mut impl Parser) -> bool {
35    p.peek(); // consume the whitespace so they aren't part of the Expression node
36    parse_expression_helper(p, OperatorPrecedence::Default)
37}
38
39#[derive(Eq, PartialEq, Ord, PartialOrd)]
40#[repr(u8)]
41enum OperatorPrecedence {
42    /// ` ?: `
43    Default,
44    /// `||`, `&&`
45    Logical,
46    /// `==` `!=` `>=` `<=` `<` `>`
47    Equality,
48    /// `+ -`
49    Add,
50    /// `* /`
51    Mul,
52    Unary,
53}
54
55fn parse_expression_helper(p: &mut impl Parser, precedence: OperatorPrecedence) -> bool {
56    let mut p = p.start_node(SyntaxKind::Expression);
57    let checkpoint = p.checkpoint();
58    let mut possible_range = false;
59    match p.nth(0).kind() {
60        SyntaxKind::Identifier => {
61            parse_qualified_name(&mut *p);
62        }
63        SyntaxKind::StringLiteral => {
64            if p.nth(0).as_str().ends_with('{') {
65                parse_template_string(&mut *p)
66            } else {
67                p.consume()
68            }
69        }
70        SyntaxKind::NumberLiteral => {
71            if p.nth(0).as_str().ends_with('.') {
72                possible_range = true;
73            }
74            p.consume()
75        }
76        SyntaxKind::ColorLiteral => p.consume(),
77        SyntaxKind::LParent => {
78            p.consume();
79            parse_expression(&mut *p);
80            p.expect(SyntaxKind::RParent);
81        }
82        SyntaxKind::LBracket => parse_array(&mut *p),
83        SyntaxKind::LBrace => parse_object_notation(&mut *p),
84        SyntaxKind::Plus | SyntaxKind::Minus | SyntaxKind::Bang => {
85            let mut p = p.start_node(SyntaxKind::UnaryOpExpression);
86            p.consume();
87            parse_expression_helper(&mut *p, OperatorPrecedence::Unary);
88        }
89        SyntaxKind::At => {
90            parse_at_keyword(&mut *p);
91        }
92        _ => {
93            p.error("invalid expression");
94            return false;
95        }
96    }
97
98    loop {
99        match p.nth(0).kind() {
100            SyntaxKind::Dot => {
101                {
102                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
103                }
104                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::MemberAccess);
105                p.consume(); // '.'
106                if possible_range && p.peek().kind() == SyntaxKind::NumberLiteral {
107                    let error = format!(
108                        "Parse error. Range expressions are not supported in Slint. You can use an integer as a model to repeat something multiple time. Eg: `for i in {} : ...`",
109                        p.peek().as_str()
110                    );
111                    p.error(error);
112                    p.consume();
113                    return false;
114                }
115                if !p.expect(SyntaxKind::Identifier) {
116                    return false;
117                }
118            }
119            SyntaxKind::LParent => {
120                {
121                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
122                }
123                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::FunctionCallExpression);
124                parse_function_arguments(&mut *p);
125            }
126            SyntaxKind::LBracket => {
127                {
128                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
129                }
130                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::IndexExpression);
131                p.expect(SyntaxKind::LBracket);
132                parse_expression(&mut *p);
133                p.expect(SyntaxKind::RBracket);
134            }
135            _ => break,
136        }
137        possible_range = false;
138    }
139
140    if precedence >= OperatorPrecedence::Mul {
141        return true;
142    }
143
144    while matches!(p.nth(0).kind(), SyntaxKind::Star | SyntaxKind::Div) {
145        {
146            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
147        }
148        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
149        p.consume();
150        parse_expression_helper(&mut *p, OperatorPrecedence::Mul);
151    }
152
153    if p.nth(0).kind() == SyntaxKind::Percent {
154        p.error("Unexpected '%'. For the unit, it should be attached to the number. If you're looking for the modulo operator, use the 'Math.mod(x, y)' function");
155        p.consume();
156        return false;
157    }
158
159    if precedence >= OperatorPrecedence::Add {
160        return true;
161    }
162
163    while matches!(p.nth(0).kind(), SyntaxKind::Plus | SyntaxKind::Minus) {
164        {
165            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
166        }
167        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
168        p.consume();
169        parse_expression_helper(&mut *p, OperatorPrecedence::Add);
170    }
171
172    if precedence > OperatorPrecedence::Equality {
173        return true;
174    }
175
176    if matches!(
177        p.nth(0).kind(),
178        SyntaxKind::LessEqual
179            | SyntaxKind::GreaterEqual
180            | SyntaxKind::EqualEqual
181            | SyntaxKind::NotEqual
182            | SyntaxKind::LAngle
183            | SyntaxKind::RAngle
184    ) {
185        if precedence == OperatorPrecedence::Equality {
186            p.error("Use parentheses to disambiguate equality expression on the same level");
187        }
188
189        {
190            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
191        }
192        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
193        p.consume();
194        parse_expression_helper(&mut *p, OperatorPrecedence::Equality);
195    }
196
197    if precedence >= OperatorPrecedence::Logical {
198        return true;
199    }
200
201    let mut prev_logical_op = None;
202    while matches!(p.nth(0).kind(), SyntaxKind::AndAnd | SyntaxKind::OrOr) {
203        if let Some(prev) = prev_logical_op {
204            if prev != p.nth(0).kind() {
205                p.error("Use parentheses to disambiguate between && and ||");
206                prev_logical_op = None;
207            }
208        } else {
209            prev_logical_op = Some(p.nth(0).kind());
210        }
211
212        {
213            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
214        }
215        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
216        p.consume();
217        parse_expression_helper(&mut *p, OperatorPrecedence::Logical);
218    }
219
220    if p.nth(0).kind() == SyntaxKind::Question {
221        {
222            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
223        }
224        let mut p = p.start_node_at(checkpoint, SyntaxKind::ConditionalExpression);
225        p.consume();
226        parse_expression(&mut *p);
227        p.expect(SyntaxKind::Colon);
228        parse_expression(&mut *p);
229    }
230    true
231}
232
233#[cfg_attr(test, parser_test)]
234/// ```test
235/// @image-url("/foo/bar.png")
236/// @linear-gradient(0deg, blue, red)
237/// @conic-gradient(blue 0deg, red 180deg)
238/// @tr("foo", bar)
239/// ```
240fn parse_at_keyword(p: &mut impl Parser) {
241    debug_assert_eq!(p.peek().kind(), SyntaxKind::At);
242    match p.nth(1).as_str() {
243        "image-url" | "image_url" => {
244            parse_image_url(p);
245        }
246        "linear-gradient" | "linear_gradient" => {
247            parse_gradient(p);
248        }
249        "radial-gradient" | "radial_gradient" => {
250            parse_gradient(p);
251        }
252        "conic-gradient" | "conic_gradient" => {
253            parse_gradient(p);
254        }
255        "tr" => {
256            parse_tr(p);
257        }
258        "markdown" => {
259            parse_markdown(p);
260        }
261        _ => {
262            p.consume();
263            p.test(SyntaxKind::Identifier); // consume the identifier, so that autocomplete works
264            p.error("Expected 'image-url', 'tr', 'linear-gradient', 'radial-gradient' or 'conic-gradient' after '@'");
265        }
266    }
267}
268
269#[cfg_attr(test, parser_test)]
270/// ```test,Array
271/// [ a, b, c , d]
272/// []
273/// [a,]
274/// [ [], [] ]
275/// ```
276fn parse_array(p: &mut impl Parser) {
277    let mut p = p.start_node(SyntaxKind::Array);
278    p.expect(SyntaxKind::LBracket);
279
280    while p.nth(0).kind() != SyntaxKind::RBracket {
281        parse_expression(&mut *p);
282        if !p.test(SyntaxKind::Comma) {
283            break;
284        }
285    }
286    p.expect(SyntaxKind::RBracket);
287}
288
289#[cfg_attr(test, parser_test)]
290/// ```test,ObjectLiteral
291/// {}
292/// {a:b}
293/// { a: "foo" , }
294/// {a:b, c: 4 + 4, d: [a,] }
295/// ```
296fn parse_object_notation(p: &mut impl Parser) {
297    let mut p = p.start_node(SyntaxKind::ObjectLiteral);
298    p.expect(SyntaxKind::LBrace);
299
300    while p.nth(0).kind() != SyntaxKind::RBrace {
301        let mut p = p.start_node(SyntaxKind::ObjectMember);
302        p.expect(SyntaxKind::Identifier);
303        p.expect(SyntaxKind::Colon);
304        parse_expression(&mut *p);
305        if !p.test(SyntaxKind::Comma) {
306            break;
307        }
308    }
309    p.expect(SyntaxKind::RBrace);
310}
311
312#[cfg_attr(test, parser_test)]
313/// ```test
314/// ()
315/// (foo)
316/// (foo, bar, foo)
317/// (foo, bar(), xx+xx,)
318/// ```
319fn parse_function_arguments(p: &mut impl Parser) {
320    p.expect(SyntaxKind::LParent);
321
322    while p.nth(0).kind() != SyntaxKind::RParent {
323        parse_expression(&mut *p);
324        if !p.test(SyntaxKind::Comma) {
325            break;
326        }
327    }
328    p.expect(SyntaxKind::RParent);
329}
330
331#[cfg_attr(test, parser_test)]
332/// ```test,StringTemplate
333/// "foo\{bar}"
334/// "foo\{4 + 5}foo"
335/// ```
336fn parse_template_string(p: &mut impl Parser) {
337    let mut p = p.start_node(SyntaxKind::StringTemplate);
338    debug_assert!(p.nth(0).as_str().ends_with("\\{"));
339    {
340        let mut p = p.start_node(SyntaxKind::Expression);
341        p.expect(SyntaxKind::StringLiteral);
342    }
343    loop {
344        parse_expression(&mut *p);
345        let peek = p.peek();
346        if peek.kind != SyntaxKind::StringLiteral || !peek.as_str().starts_with('}') {
347            p.error("Error while parsing string template")
348        }
349        let mut p = p.start_node(SyntaxKind::Expression);
350        let cont = peek.as_str().ends_with('{');
351        p.consume();
352        if !cont {
353            break;
354        }
355    }
356}
357
358#[cfg_attr(test, parser_test)]
359/// ```test,AtGradient
360/// @linear-gradient(#e66465, #9198e5)
361/// @linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c)
362/// @linear-gradient(to left, #333, #333 50%, #eee 75%, #333 75%)
363/// @linear-gradient(217deg, rgba(255,0,0,0.8), rgba(255,0,0,0) 70.71%)
364/// @linear_gradient(217deg, rgba(255,0,0,0.8), rgba(255,0,0,0) 70.71%)
365/// @radial-gradient(circle, #e66465, blue 50%, #9198e5)
366/// @conic-gradient(#e66465 0deg, #9198e5 180deg, #e66465 360deg)
367/// @conic-gradient(red 0deg, green 120deg, blue 240deg, red 360deg)
368/// @conic-gradient(#fff 0turn, #000 0.5turn, #fff 1turn)
369/// @conic_gradient(red 0rad, blue 3.14159rad, red 6.28318rad)
370/// ```
371fn parse_gradient(p: &mut impl Parser) {
372    let mut p = p.start_node(SyntaxKind::AtGradient);
373    p.expect(SyntaxKind::At);
374    debug_assert!(p.peek().as_str().ends_with("gradient"));
375    p.expect(SyntaxKind::Identifier); //eg "linear-gradient"
376
377    p.expect(SyntaxKind::LParent);
378
379    while !p.test(SyntaxKind::RParent) {
380        if !parse_expression(&mut *p) {
381            return;
382        }
383        p.test(SyntaxKind::Comma);
384    }
385}
386
387#[cfg_attr(test, parser_test)]
388/// ```test,AtTr
389/// @tr("foo")
390/// @tr("foo{0}", bar(42))
391/// @tr("context" => "ccc{}", 0)
392/// @tr("xxx" => "ccc{n}" | "ddd{}" % 42, 45)
393/// ```
394fn parse_tr(p: &mut impl Parser) {
395    let mut p = p.start_node(SyntaxKind::AtTr);
396    p.expect(SyntaxKind::At);
397    debug_assert_eq!(p.peek().as_str(), "tr");
398    p.expect(SyntaxKind::Identifier); //"tr"
399    p.expect(SyntaxKind::LParent);
400
401    let checkpoint = p.checkpoint();
402
403    fn consume_literal(p: &mut impl Parser) -> bool {
404        let peek = p.peek();
405        if peek.kind() != SyntaxKind::StringLiteral
406            || !peek.as_str().starts_with('"')
407            || !peek.as_str().ends_with('"')
408        {
409            p.error("Expected plain string literal");
410            return false;
411        }
412        p.expect(SyntaxKind::StringLiteral)
413    }
414
415    if !consume_literal(&mut *p) {
416        return;
417    }
418
419    if p.test(SyntaxKind::FatArrow) {
420        drop(p.start_node_at(checkpoint, SyntaxKind::TrContext));
421        if !consume_literal(&mut *p) {
422            return;
423        }
424    }
425
426    if p.peek().kind() == SyntaxKind::Pipe {
427        let mut p = p.start_node(SyntaxKind::TrPlural);
428        p.consume();
429        if !consume_literal(&mut *p) || !p.expect(SyntaxKind::Percent) {
430            let _ = p.start_node(SyntaxKind::Expression);
431            return;
432        }
433        parse_expression(&mut *p);
434    }
435
436    while p.test(SyntaxKind::Comma) {
437        if !parse_expression(&mut *p) {
438            break;
439        }
440    }
441    p.expect(SyntaxKind::RParent);
442}
443
444/// ```test,AtTr
445/// @markdown("foo")
446/// @markdown("foo{}", bar(42))
447/// @markdown("foo{0}", bar(42))
448/// @markdown("foo{1}{0}", 555, bar(42))
449/// @markdown("foo{1}{0}{1}", 555, bar(42))
450/// ```
451fn parse_markdown(p: &mut impl Parser) {
452    let mut p = p.start_node(SyntaxKind::AtMarkdown);
453    p.expect(SyntaxKind::At);
454    debug_assert!(p.peek().as_str().ends_with("markdown"));
455    p.expect(SyntaxKind::Identifier); //eg "markdown"
456    p.expect(SyntaxKind::LParent);
457
458    fn consume_literal(p: &mut impl Parser) -> bool {
459        let peek = p.peek();
460        if peek.kind() != SyntaxKind::StringLiteral
461            || !peek.as_str().starts_with('"')
462            || !peek.as_str().ends_with('"')
463        {
464            p.error("Expected plain string literal");
465            return false;
466        }
467        p.expect(SyntaxKind::StringLiteral)
468    }
469
470    if !consume_literal(&mut *p) {
471        return;
472    }
473
474    while p.test(SyntaxKind::Comma) {
475        if !parse_expression(&mut *p) {
476            break;
477        }
478    }
479    p.expect(SyntaxKind::RParent);
480}
481
482#[cfg_attr(test, parser_test)]
483/// ```test,AtImageUrl
484/// @image-url("foo.png")
485/// @image-url("foo.png",)
486/// @image-url("foo.png", nine-slice(1 2 3 4))
487/// @image-url("foo.png", nine-slice(1))
488/// ```
489fn parse_image_url(p: &mut impl Parser) {
490    let mut p = p.start_node(SyntaxKind::AtImageUrl);
491    p.consume(); // "@"
492    p.consume(); // "image-url"
493    if !(p.expect(SyntaxKind::LParent)) {
494        return;
495    }
496    let peek = p.peek();
497    if peek.kind() != SyntaxKind::StringLiteral {
498        p.error("@image-url must contain a plain path as a string literal");
499        p.until(SyntaxKind::RParent);
500        return;
501    }
502    if !peek.as_str().starts_with('"') || !peek.as_str().ends_with('"') {
503        p.error("@image-url must contain a plain path as a string literal, without any '\\{}' expressions");
504        p.until(SyntaxKind::RParent);
505        return;
506    }
507    p.expect(SyntaxKind::StringLiteral);
508    if !p.test(SyntaxKind::Comma) {
509        if !p.test(SyntaxKind::RParent) {
510            p.error("Expected ')' or ','");
511            p.until(SyntaxKind::RParent);
512        }
513        return;
514    }
515    if p.test(SyntaxKind::RParent) {
516        return;
517    }
518    if p.peek().as_str() != "nine-slice" {
519        p.error("Expected 'nine-slice(...)' argument");
520        p.until(SyntaxKind::RParent);
521        return;
522    }
523    p.consume();
524    if !p.expect(SyntaxKind::LParent) {
525        p.until(SyntaxKind::RParent);
526        return;
527    }
528    let mut count = 0;
529    loop {
530        match p.peek().kind() {
531            SyntaxKind::RParent => {
532                if count != 1 && count != 2 && count != 4 {
533                    p.error("Expected 1 or 2 or 4 numbers");
534                }
535                p.consume();
536                break;
537            }
538            SyntaxKind::NumberLiteral => {
539                count += 1;
540                p.consume();
541            }
542            SyntaxKind::Comma | SyntaxKind::Colon => {
543                p.error("Arguments of nine-slice need to be separated by spaces");
544                p.until(SyntaxKind::RParent);
545                break;
546            }
547            _ => {
548                p.error("Expected number literal or ')'");
549                p.until(SyntaxKind::RParent);
550                break;
551            }
552        }
553    }
554    if !p.expect(SyntaxKind::RParent) {
555        p.until(SyntaxKind::RParent);
556    }
557}