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!(
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)]
234fn 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); p.error("Expected 'image-url', 'tr', 'linear-gradient', 'radial-gradient' or 'conic-gradient' after '@'");
265 }
266 }
267}
268
269#[cfg_attr(test, parser_test)]
270fn 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)]
290fn 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)]
313fn 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)]
332fn 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)]
359fn 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); 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)]
388fn 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); 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
444fn 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); 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)]
483fn parse_image_url(p: &mut impl Parser) {
490 let mut p = p.start_node(SyntaxKind::AtImageUrl);
491 p.consume(); p.consume(); 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}