i_slint_compiler/parser/
expressions.rs1use super::document::parse_qualified_name;
5use super::prelude::*;
6
7#[cfg_attr(test, parser_test)]
8pub fn parse_expression(p: &mut impl Parser) -> bool {
35 p.peek(); parse_expression_helper(p, OperatorPrecedence::Default)
37}
38
39#[derive(Eq, PartialEq, Ord, PartialOrd)]
40#[repr(u8)]
41enum OperatorPrecedence {
42 Default,
44 Logical,
46 Equality,
48 Add,
50 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(); 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)]
231fn 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); p.error("Expected 'image-url', 'tr', 'linear-gradient' or 'radial-gradient' after '@'");
255 }
256 }
257}
258
259#[cfg_attr(test, parser_test)]
260fn 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)]
280fn 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)]
303fn 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)]
322fn 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)]
349fn 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); 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)]
374fn 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); 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)]
431fn parse_image_url(p: &mut impl Parser) {
438 let mut p = p.start_node(SyntaxKind::AtImageUrl);
439 p.consume(); p.consume(); 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}