sixtyfps_compilerlib/parser/
expressions.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
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    parse_expression_helper(p, OperatorPrecedence::Default)
36}
37
38#[derive(Eq, PartialEq, Ord, PartialOrd)]
39#[repr(u8)]
40enum OperatorPrecedence {
41    /// ` ?: `
42    Default,
43    /// `||`, `&&`
44    Logical,
45    /// `==` `!=` `>=` `<=` `<` `>`
46    Equality,
47    /// `+ -`
48    Add,
49    /// `* /`
50    Mul,
51    Unary,
52}
53
54fn parse_expression_helper(p: &mut impl Parser, precedence: OperatorPrecedence) -> bool {
55    let mut p = p.start_node(SyntaxKind::Expression);
56    let checkpoint = p.checkpoint();
57    match p.nth(0).kind() {
58        SyntaxKind::Identifier => {
59            parse_qualified_name(&mut *p);
60        }
61        SyntaxKind::StringLiteral => {
62            if p.nth(0).as_str().ends_with('{') {
63                parse_template_string(&mut *p)
64            } else {
65                p.consume()
66            }
67        }
68        SyntaxKind::NumberLiteral => p.consume(),
69        SyntaxKind::ColorLiteral => p.consume(),
70        SyntaxKind::LParent => {
71            p.consume();
72            parse_expression(&mut *p);
73            p.expect(SyntaxKind::RParent);
74        }
75        SyntaxKind::LBracket => parse_array(&mut *p),
76        SyntaxKind::LBrace => parse_object_notation(&mut *p),
77        SyntaxKind::Plus | SyntaxKind::Minus | SyntaxKind::Bang => {
78            let mut p = p.start_node(SyntaxKind::UnaryOpExpression);
79            p.consume();
80            parse_expression_helper(&mut *p, OperatorPrecedence::Unary);
81        }
82        SyntaxKind::At => {
83            parse_at_keyword(&mut *p);
84        }
85        _ => {
86            p.error("invalid expression");
87            return false;
88        }
89    }
90
91    loop {
92        match p.nth(0).kind() {
93            SyntaxKind::Dot => {
94                {
95                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
96                }
97                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::MemberAccess);
98                p.consume(); // '.'
99                if !p.expect(SyntaxKind::Identifier) {
100                    return false;
101                }
102            }
103            SyntaxKind::LParent => {
104                {
105                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
106                }
107                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::FunctionCallExpression);
108                parse_function_arguments(&mut *p);
109            }
110            SyntaxKind::LBracket => {
111                {
112                    let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
113                }
114                let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::IndexExpression);
115                p.expect(SyntaxKind::LBracket);
116                parse_expression(&mut *p);
117                p.expect(SyntaxKind::RBracket);
118            }
119            _ => break,
120        }
121    }
122
123    if precedence >= OperatorPrecedence::Mul {
124        return true;
125    }
126
127    while matches!(p.nth(0).kind(), SyntaxKind::Star | SyntaxKind::Div) {
128        {
129            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
130        }
131        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
132        p.consume();
133        parse_expression_helper(&mut *p, OperatorPrecedence::Mul);
134    }
135
136    if precedence >= OperatorPrecedence::Add {
137        return true;
138    }
139
140    while matches!(p.nth(0).kind(), SyntaxKind::Plus | SyntaxKind::Minus) {
141        {
142            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
143        }
144        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
145        p.consume();
146        parse_expression_helper(&mut *p, OperatorPrecedence::Add);
147    }
148
149    if precedence > OperatorPrecedence::Equality {
150        return true;
151    }
152
153    if matches!(
154        p.nth(0).kind(),
155        SyntaxKind::LessEqual
156            | SyntaxKind::GreaterEqual
157            | SyntaxKind::EqualEqual
158            | SyntaxKind::NotEqual
159            | SyntaxKind::LAngle
160            | SyntaxKind::RAngle
161    ) {
162        if precedence == OperatorPrecedence::Equality {
163            p.error("Use parentheses to disambiguate equality expression on the same level");
164        }
165
166        {
167            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
168        }
169        let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
170        p.consume();
171        parse_expression_helper(&mut *p, OperatorPrecedence::Equality);
172    }
173
174    if precedence >= OperatorPrecedence::Logical {
175        return true;
176    }
177
178    let mut prev_logical_op = None;
179    while matches!(p.nth(0).kind(), SyntaxKind::AndAnd | SyntaxKind::OrOr) {
180        if let Some(prev) = prev_logical_op {
181            if prev != p.nth(0).kind() {
182                p.error("Use parentheses to disambiguate between && and ||");
183                prev_logical_op = None;
184            }
185        } else {
186            prev_logical_op = Some(p.nth(0).kind());
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::Logical);
195    }
196
197    if p.nth(0).kind() == SyntaxKind::Question {
198        {
199            let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
200        }
201        let mut p = p.start_node_at(checkpoint, SyntaxKind::ConditionalExpression);
202        p.consume();
203        parse_expression(&mut *p);
204        p.expect(SyntaxKind::Colon);
205        parse_expression(&mut *p);
206    }
207    true
208}
209
210#[cfg_attr(test, parser_test)]
211/// ```test
212/// @image-url("/foo/bar.png")
213/// @linear-gradient(0deg, blue, red)
214/// ```
215fn parse_at_keyword(p: &mut impl Parser) {
216    debug_assert_eq!(p.peek().kind(), SyntaxKind::At);
217    match p.nth(1).as_str() {
218        "image-url" | "image_url" => {
219            let mut p = p.start_node(SyntaxKind::AtImageUrl);
220            p.consume(); // "@"
221            p.consume(); // "image-url"
222            p.expect(SyntaxKind::LParent);
223            p.expect(SyntaxKind::StringLiteral);
224            p.expect(SyntaxKind::RParent);
225        }
226        "linear-gradient" | "linear_gradient" => {
227            parse_at_linear_gradient(p);
228        }
229        _ => {
230            p.consume();
231            p.error("Expected 'image-url' or 'linear-gradient' after '@'");
232        }
233    }
234}
235
236#[cfg_attr(test, parser_test)]
237/// ```test,Array
238/// [ a, b, c , d]
239/// []
240/// [a,]
241/// [ [], [] ]
242/// ```
243fn parse_array(p: &mut impl Parser) {
244    let mut p = p.start_node(SyntaxKind::Array);
245    p.expect(SyntaxKind::LBracket);
246
247    while p.nth(0).kind() != SyntaxKind::RBracket {
248        parse_expression(&mut *p);
249        if !p.test(SyntaxKind::Comma) {
250            break;
251        }
252    }
253    p.expect(SyntaxKind::RBracket);
254}
255
256#[cfg_attr(test, parser_test)]
257/// ```test,ObjectLiteral
258/// {}
259/// {a:b}
260/// { a: "foo" , }
261/// {a:b, c: 4 + 4, d: [a,] }
262/// ```
263fn parse_object_notation(p: &mut impl Parser) {
264    let mut p = p.start_node(SyntaxKind::ObjectLiteral);
265    p.expect(SyntaxKind::LBrace);
266
267    while p.nth(0).kind() != SyntaxKind::RBrace {
268        let mut p = p.start_node(SyntaxKind::ObjectMember);
269        p.expect(SyntaxKind::Identifier);
270        p.expect(SyntaxKind::Colon);
271        parse_expression(&mut *p);
272        if !p.test(SyntaxKind::Comma) {
273            break;
274        }
275    }
276    p.expect(SyntaxKind::RBrace);
277}
278
279#[cfg_attr(test, parser_test)]
280/// ```test
281/// ()
282/// (foo)
283/// (foo, bar, foo)
284/// (foo, bar(), xx+xx,)
285/// ```
286fn parse_function_arguments(p: &mut impl Parser) {
287    p.expect(SyntaxKind::LParent);
288
289    while p.nth(0).kind() != SyntaxKind::RParent {
290        parse_expression(&mut *p);
291        if !p.test(SyntaxKind::Comma) {
292            break;
293        }
294    }
295    p.expect(SyntaxKind::RParent);
296}
297
298#[cfg_attr(test, parser_test)]
299/// ```test,StringTemplate
300/// "foo\{bar}"
301/// "foo\{4 + 5}foo"
302/// ```
303fn parse_template_string(p: &mut impl Parser) {
304    let mut p = p.start_node(SyntaxKind::StringTemplate);
305    debug_assert!(p.nth(0).as_str().ends_with("\\{"));
306    {
307        let mut p = p.start_node(SyntaxKind::Expression);
308        p.consume();
309    }
310    loop {
311        parse_expression(&mut *p);
312        let peek = p.peek();
313        if peek.kind != SyntaxKind::StringLiteral || !peek.as_str().starts_with('}') {
314            p.error("Error while parsing string template")
315        }
316        let mut p = p.start_node(SyntaxKind::Expression);
317        let cont = peek.as_str().ends_with('{');
318        p.consume();
319        if !cont {
320            break;
321        }
322    }
323}
324
325#[cfg_attr(test, parser_test)]
326/// ```test,AtLinearGradient
327/// @linear-gradient(#e66465, #9198e5)
328/// @linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c)
329/// @linear-gradient(to left, #333, #333 50%, #eee 75%, #333 75%)
330/// @linear-gradient(217deg, rgba(255,0,0,0.8), rgba(255,0,0,0) 70.71%)
331/// @linear_gradient(217deg, rgba(255,0,0,0.8), rgba(255,0,0,0) 70.71%)
332/// ```
333fn parse_at_linear_gradient(p: &mut impl Parser) {
334    let mut p = p.start_node(SyntaxKind::AtLinearGradient);
335    p.expect(SyntaxKind::At);
336    debug_assert!(p.peek().as_str() == "linear-gradient" || p.peek().as_str() == "linear_gradient");
337    p.consume(); //"linear-gradient"
338
339    p.expect(SyntaxKind::LParent);
340
341    while !p.test(SyntaxKind::RParent) {
342        if !parse_expression(&mut *p) {
343            return;
344        }
345        p.test(SyntaxKind::Comma);
346    }
347}