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!("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 {} : ...`", p.peek().as_str());
108                    p.error(error);
109                    p.consume();
110                    return false;
111                }
112                if !p.expect(SyntaxKind::Identifier) {
113                    return false;
114                }
115            }
116            SyntaxKind::LParent => {
117                {
118                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
119                }
120                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::FunctionCallExpression);
121                parse_function_arguments(&mut *p);
122            }
123            SyntaxKind::LBracket => {
124                {
125                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
126                }
127                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::IndexExpression);
128                p.expect(SyntaxKind::LBracket);
129                parse_expression(&mut *p);
130                p.expect(SyntaxKind::RBracket);
131            }
132            _ => break,
133        }
134        possible_range = false;
135    }
136
137    if precedence >= OperatorPrecedence::Mul {
138        return true;
139    }
140
141    while matches!(p.nth(0).kind(), SyntaxKind::Star | SyntaxKind::Div) {
142        {
143            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
144        }
145        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
146        p.consume();
147        parse_expression_helper(&mut *p, OperatorPrecedence::Mul);
148    }
149
150    if p.nth(0).kind() == SyntaxKind::Percent {
151        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");
152        p.consume();
153        return false;
154    }
155
156    if precedence >= OperatorPrecedence::Add {
157        return true;
158    }
159
160    while matches!(p.nth(0).kind(), SyntaxKind::Plus | SyntaxKind::Minus) {
161        {
162            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
163        }
164        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
165        p.consume();
166        parse_expression_helper(&mut *p, OperatorPrecedence::Add);
167    }
168
169    if precedence > OperatorPrecedence::Equality {
170        return true;
171    }
172
173    if matches!(
174        p.nth(0).kind(),
175        SyntaxKind::LessEqual
176            | SyntaxKind::GreaterEqual
177            | SyntaxKind::EqualEqual
178            | SyntaxKind::NotEqual
179            | SyntaxKind::LAngle
180            | SyntaxKind::RAngle
181    ) {
182        if precedence == OperatorPrecedence::Equality {
183            p.error("Use parentheses to disambiguate equality expression on the same level");
184        }
185
186        {
187            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
188        }
189        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
190        p.consume();
191        parse_expression_helper(&mut *p, OperatorPrecedence::Equality);
192    }
193
194    if precedence >= OperatorPrecedence::Logical {
195        return true;
196    }
197
198    let mut prev_logical_op = None;
199    while matches!(p.nth(0).kind(), SyntaxKind::AndAnd | SyntaxKind::OrOr) {
200        if let Some(prev) = prev_logical_op {
201            if prev != p.nth(0).kind() {
202                p.error("Use parentheses to disambiguate between && and ||");
203                prev_logical_op = None;
204            }
205        } else {
206            prev_logical_op = Some(p.nth(0).kind());
207        }
208
209        {
210            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
211        }
212        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
213        p.consume();
214        parse_expression_helper(&mut *p, OperatorPrecedence::Logical);
215    }
216
217    if p.nth(0).kind() == SyntaxKind::Question {
218        {
219            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
220        }
221        let mut p = p.start_node_at(checkpoint, SyntaxKind::ConditionalExpression);
222        p.consume();
223        parse_expression(&mut *p);
224        p.expect(SyntaxKind::Colon);
225        parse_expression(&mut *p);
226    }
227    true
228}
229
230#[cfg_attr(test, parser_test)]
231/// ```test
232/// @image-url("/foo/bar.png")
233/// @linear-gradient(0deg, blue, red)
234/// @tr("foo", bar)
235/// ```
236fn parse_at_keyword(p: &mut impl Parser) {
237    debug_assert_eq!(p.peek().kind(), SyntaxKind::At);
238    match p.nth(1).as_str() {
239        "image-url" | "image_url" => {
240            parse_image_url(p);
241        }
242        "linear-gradient" | "linear_gradient" => {
243            parse_gradient(p);
244        }
245        "radial-gradient" | "radial_gradient" => {
246            parse_gradient(p);
247        }
248        "tr" => {
249            parse_tr(p);
250        }
251        _ => {
252            p.consume();
253            p.test(SyntaxKind::Identifier); // consume the identifier, so that autocomplete works
254            p.error("Expected 'image-url', 'tr', 'linear-gradient' or 'radial-gradient' after '@'");
255        }
256    }
257}
258
259#[cfg_attr(test, parser_test)]
260/// ```test,Array
261/// [ a, b, c , d]
262/// []
263/// [a,]
264/// [ [], [] ]
265/// ```
266fn parse_array(p: &mut impl Parser) {
267    let mut p = p.start_node(SyntaxKind::Array);
268    p.expect(SyntaxKind::LBracket);
269
270    while p.nth(0).kind() != SyntaxKind::RBracket {
271        parse_expression(&mut *p);
272        if !p.test(SyntaxKind::Comma) {
273            break;
274        }
275    }
276    p.expect(SyntaxKind::RBracket);
277}
278
279#[cfg_attr(test, parser_test)]
280/// ```test,ObjectLiteral
281/// {}
282/// {a:b}
283/// { a: "foo" , }
284/// {a:b, c: 4 + 4, d: [a,] }
285/// ```
286fn parse_object_notation(p: &mut impl Parser) {
287    let mut p = p.start_node(SyntaxKind::ObjectLiteral);
288    p.expect(SyntaxKind::LBrace);
289
290    while p.nth(0).kind() != SyntaxKind::RBrace {
291        let mut p = p.start_node(SyntaxKind::ObjectMember);
292        p.expect(SyntaxKind::Identifier);
293        p.expect(SyntaxKind::Colon);
294        parse_expression(&mut *p);
295        if !p.test(SyntaxKind::Comma) {
296            break;
297        }
298    }
299    p.expect(SyntaxKind::RBrace);
300}
301
302#[cfg_attr(test, parser_test)]
303/// ```test
304/// ()
305/// (foo)
306/// (foo, bar, foo)
307/// (foo, bar(), xx+xx,)
308/// ```
309fn parse_function_arguments(p: &mut impl Parser) {
310    p.expect(SyntaxKind::LParent);
311
312    while p.nth(0).kind() != SyntaxKind::RParent {
313        parse_expression(&mut *p);
314        if !p.test(SyntaxKind::Comma) {
315            break;
316        }
317    }
318    p.expect(SyntaxKind::RParent);
319}
320
321#[cfg_attr(test, parser_test)]
322/// ```test,StringTemplate
323/// "foo\{bar}"
324/// "foo\{4 + 5}foo"
325/// ```
326fn parse_template_string(p: &mut impl Parser) {
327    let mut p = p.start_node(SyntaxKind::StringTemplate);
328    debug_assert!(p.nth(0).as_str().ends_with("\\{"));
329    {
330        let mut p = p.start_node(SyntaxKind::Expression);
331        p.expect(SyntaxKind::StringLiteral);
332    }
333    loop {
334        parse_expression(&mut *p);
335        let peek = p.peek();
336        if peek.kind != SyntaxKind::StringLiteral || !peek.as_str().starts_with('}') {
337            p.error("Error while parsing string template")
338        }
339        let mut p = p.start_node(SyntaxKind::Expression);
340        let cont = peek.as_str().ends_with('{');
341        p.consume();
342        if !cont {
343            break;
344        }
345    }
346}
347
348#[cfg_attr(test, parser_test)]
349/// ```test,AtGradient
350/// @linear-gradient(#e66465, #9198e5)
351/// @linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c)
352/// @linear-gradient(to left, #333, #333 50%, #eee 75%, #333 75%)
353/// @linear-gradient(217deg, rgba(255,0,0,0.8), rgba(255,0,0,0) 70.71%)
354/// @linear_gradient(217deg, rgba(255,0,0,0.8), rgba(255,0,0,0) 70.71%)
355/// @radial-gradient(circle, #e66465, blue 50%, #9198e5)
356/// ```
357fn parse_gradient(p: &mut impl Parser) {
358    let mut p = p.start_node(SyntaxKind::AtGradient);
359    p.expect(SyntaxKind::At);
360    debug_assert!(p.peek().as_str().ends_with("gradient"));
361    p.expect(SyntaxKind::Identifier); //eg "linear-gradient"
362
363    p.expect(SyntaxKind::LParent);
364
365    while !p.test(SyntaxKind::RParent) {
366        if !parse_expression(&mut *p) {
367            return;
368        }
369        p.test(SyntaxKind::Comma);
370    }
371}
372
373#[cfg_attr(test, parser_test)]
374/// ```test,AtTr
375/// @tr("foo")
376/// @tr("foo{0}", bar(42))
377/// @tr("context" => "ccc{}", 0)
378/// @tr("xxx" => "ccc{n}" | "ddd{}" % 42, 45)
379/// ```
380fn parse_tr(p: &mut impl Parser) {
381    let mut p = p.start_node(SyntaxKind::AtTr);
382    p.expect(SyntaxKind::At);
383    debug_assert_eq!(p.peek().as_str(), "tr");
384    p.expect(SyntaxKind::Identifier); //"tr"
385    p.expect(SyntaxKind::LParent);
386
387    let checkpoint = p.checkpoint();
388
389    fn consume_literal(p: &mut impl Parser) -> bool {
390        let peek = p.peek();
391        if peek.kind() != SyntaxKind::StringLiteral
392            || !peek.as_str().starts_with('"')
393            || !peek.as_str().ends_with('"')
394        {
395            p.error("Expected plain string literal");
396            return false;
397        }
398        p.expect(SyntaxKind::StringLiteral)
399    }
400
401    if !consume_literal(&mut *p) {
402        return;
403    }
404
405    if p.test(SyntaxKind::FatArrow) {
406        drop(p.start_node_at(checkpoint, SyntaxKind::TrContext));
407        if !consume_literal(&mut *p) {
408            return;
409        }
410    }
411
412    if p.peek().kind() == SyntaxKind::Pipe {
413        let mut p = p.start_node(SyntaxKind::TrPlural);
414        p.consume();
415        if !consume_literal(&mut *p) || !p.expect(SyntaxKind::Percent) {
416            let _ = p.start_node(SyntaxKind::Expression);
417            return;
418        }
419        parse_expression(&mut *p);
420    }
421
422    while p.test(SyntaxKind::Comma) {
423        if !parse_expression(&mut *p) {
424            break;
425        }
426    }
427    p.expect(SyntaxKind::RParent);
428}
429
430#[cfg_attr(test, parser_test)]
431/// ```test,AtImageUrl
432/// @image-url("foo.png")
433/// @image-url("foo.png",)
434/// @image-url("foo.png", nine-slice(1 2 3 4))
435/// @image-url("foo.png", nine-slice(1))
436/// ```
437fn parse_image_url(p: &mut impl Parser) {
438    let mut p = p.start_node(SyntaxKind::AtImageUrl);
439    p.consume(); // "@"
440    p.consume(); // "image-url"
441    if !(p.expect(SyntaxKind::LParent)) {
442        return;
443    }
444    let peek = p.peek();
445    if peek.kind() != SyntaxKind::StringLiteral {
446        p.error("@image-url must contain a plain path as a string literal");
447        p.until(SyntaxKind::RParent);
448        return;
449    }
450    if !peek.as_str().starts_with('"') || !peek.as_str().ends_with('"') {
451        p.error("@image-url must contain a plain path as a string literal, without any '\\{}' expressions");
452        p.until(SyntaxKind::RParent);
453        return;
454    }
455    p.expect(SyntaxKind::StringLiteral);
456    if !p.test(SyntaxKind::Comma) {
457        if !p.test(SyntaxKind::RParent) {
458            p.error("Expected ')' or ','");
459            p.until(SyntaxKind::RParent);
460        }
461        return;
462    }
463    if p.test(SyntaxKind::RParent) {
464        return;
465    }
466    if p.peek().as_str() != "nine-slice" {
467        p.error("Expected 'nine-slice(...)' argument");
468        p.until(SyntaxKind::RParent);
469        return;
470    }
471    p.consume();
472    if !p.expect(SyntaxKind::LParent) {
473        p.until(SyntaxKind::RParent);
474        return;
475    }
476    let mut count = 0;
477    loop {
478        match p.peek().kind() {
479            SyntaxKind::RParent => {
480                if count != 1 && count != 2 && count != 4 {
481                    p.error("Expected 1 or 2 or 4 numbers");
482                }
483                p.consume();
484                break;
485            }
486            SyntaxKind::NumberLiteral => {
487                count += 1;
488                p.consume();
489            }
490            SyntaxKind::Comma | SyntaxKind::Colon => {
491                p.error("Arguments of nine-slice need to be separated by spaces");
492                p.until(SyntaxKind::RParent);
493                break;
494            }
495            _ => {
496                p.error("Expected number literal or ')'");
497                p.until(SyntaxKind::RParent);
498                break;
499            }
500        }
501    }
502    if !p.expect(SyntaxKind::RParent) {
503        p.until(SyntaxKind::RParent);
504    }
505}