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) {
238 debug_assert_eq!(p.peek().kind(), SyntaxKind::At);
239 match p.nth(1).as_str() {
240 "image-url" | "image_url" => {
241 parse_image_url(p);
242 }
243 "linear-gradient" | "linear_gradient" => {
244 parse_gradient(p);
245 }
246 "radial-gradient" | "radial_gradient" => {
247 parse_gradient(p);
248 }
249 "conic-gradient" | "conic_gradient" => {
250 parse_gradient(p);
251 }
252 "tr" => {
253 parse_tr(p);
254 }
255 _ => {
256 p.consume();
257 p.test(SyntaxKind::Identifier); p.error("Expected 'image-url', 'tr', 'linear-gradient', 'radial-gradient' or 'conic-gradient' after '@'");
259 }
260 }
261}
262
263#[cfg_attr(test, parser_test)]
264fn parse_array(p: &mut impl Parser) {
271 let mut p = p.start_node(SyntaxKind::Array);
272 p.expect(SyntaxKind::LBracket);
273
274 while p.nth(0).kind() != SyntaxKind::RBracket {
275 parse_expression(&mut *p);
276 if !p.test(SyntaxKind::Comma) {
277 break;
278 }
279 }
280 p.expect(SyntaxKind::RBracket);
281}
282
283#[cfg_attr(test, parser_test)]
284fn parse_object_notation(p: &mut impl Parser) {
291 let mut p = p.start_node(SyntaxKind::ObjectLiteral);
292 p.expect(SyntaxKind::LBrace);
293
294 while p.nth(0).kind() != SyntaxKind::RBrace {
295 let mut p = p.start_node(SyntaxKind::ObjectMember);
296 p.expect(SyntaxKind::Identifier);
297 p.expect(SyntaxKind::Colon);
298 parse_expression(&mut *p);
299 if !p.test(SyntaxKind::Comma) {
300 break;
301 }
302 }
303 p.expect(SyntaxKind::RBrace);
304}
305
306#[cfg_attr(test, parser_test)]
307fn parse_function_arguments(p: &mut impl Parser) {
314 p.expect(SyntaxKind::LParent);
315
316 while p.nth(0).kind() != SyntaxKind::RParent {
317 parse_expression(&mut *p);
318 if !p.test(SyntaxKind::Comma) {
319 break;
320 }
321 }
322 p.expect(SyntaxKind::RParent);
323}
324
325#[cfg_attr(test, parser_test)]
326fn parse_template_string(p: &mut impl Parser) {
331 let mut p = p.start_node(SyntaxKind::StringTemplate);
332 debug_assert!(p.nth(0).as_str().ends_with("\\{"));
333 {
334 let mut p = p.start_node(SyntaxKind::Expression);
335 p.expect(SyntaxKind::StringLiteral);
336 }
337 loop {
338 parse_expression(&mut *p);
339 let peek = p.peek();
340 if peek.kind != SyntaxKind::StringLiteral || !peek.as_str().starts_with('}') {
341 p.error("Error while parsing string template")
342 }
343 let mut p = p.start_node(SyntaxKind::Expression);
344 let cont = peek.as_str().ends_with('{');
345 p.consume();
346 if !cont {
347 break;
348 }
349 }
350}
351
352#[cfg_attr(test, parser_test)]
353fn parse_gradient(p: &mut impl Parser) {
366 let mut p = p.start_node(SyntaxKind::AtGradient);
367 p.expect(SyntaxKind::At);
368 debug_assert!(p.peek().as_str().ends_with("gradient"));
369 p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
372
373 while !p.test(SyntaxKind::RParent) {
374 if !parse_expression(&mut *p) {
375 return;
376 }
377 p.test(SyntaxKind::Comma);
378 }
379}
380
381#[cfg_attr(test, parser_test)]
382fn parse_tr(p: &mut impl Parser) {
389 let mut p = p.start_node(SyntaxKind::AtTr);
390 p.expect(SyntaxKind::At);
391 debug_assert_eq!(p.peek().as_str(), "tr");
392 p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
394
395 let checkpoint = p.checkpoint();
396
397 fn consume_literal(p: &mut impl Parser) -> bool {
398 let peek = p.peek();
399 if peek.kind() != SyntaxKind::StringLiteral
400 || !peek.as_str().starts_with('"')
401 || !peek.as_str().ends_with('"')
402 {
403 p.error("Expected plain string literal");
404 return false;
405 }
406 p.expect(SyntaxKind::StringLiteral)
407 }
408
409 if !consume_literal(&mut *p) {
410 return;
411 }
412
413 if p.test(SyntaxKind::FatArrow) {
414 drop(p.start_node_at(checkpoint, SyntaxKind::TrContext));
415 if !consume_literal(&mut *p) {
416 return;
417 }
418 }
419
420 if p.peek().kind() == SyntaxKind::Pipe {
421 let mut p = p.start_node(SyntaxKind::TrPlural);
422 p.consume();
423 if !consume_literal(&mut *p) || !p.expect(SyntaxKind::Percent) {
424 let _ = p.start_node(SyntaxKind::Expression);
425 return;
426 }
427 parse_expression(&mut *p);
428 }
429
430 while p.test(SyntaxKind::Comma) {
431 if !parse_expression(&mut *p) {
432 break;
433 }
434 }
435 p.expect(SyntaxKind::RParent);
436}
437
438#[cfg_attr(test, parser_test)]
439fn parse_image_url(p: &mut impl Parser) {
446 let mut p = p.start_node(SyntaxKind::AtImageUrl);
447 p.consume(); p.consume(); if !(p.expect(SyntaxKind::LParent)) {
450 return;
451 }
452 let peek = p.peek();
453 if peek.kind() != SyntaxKind::StringLiteral {
454 p.error("@image-url must contain a plain path as a string literal");
455 p.until(SyntaxKind::RParent);
456 return;
457 }
458 if !peek.as_str().starts_with('"') || !peek.as_str().ends_with('"') {
459 p.error("@image-url must contain a plain path as a string literal, without any '\\{}' expressions");
460 p.until(SyntaxKind::RParent);
461 return;
462 }
463 p.expect(SyntaxKind::StringLiteral);
464 if !p.test(SyntaxKind::Comma) {
465 if !p.test(SyntaxKind::RParent) {
466 p.error("Expected ')' or ','");
467 p.until(SyntaxKind::RParent);
468 }
469 return;
470 }
471 if p.test(SyntaxKind::RParent) {
472 return;
473 }
474 if p.peek().as_str() != "nine-slice" {
475 p.error("Expected 'nine-slice(...)' argument");
476 p.until(SyntaxKind::RParent);
477 return;
478 }
479 p.consume();
480 if !p.expect(SyntaxKind::LParent) {
481 p.until(SyntaxKind::RParent);
482 return;
483 }
484 let mut count = 0;
485 loop {
486 match p.peek().kind() {
487 SyntaxKind::RParent => {
488 if count != 1 && count != 2 && count != 4 {
489 p.error("Expected 1 or 2 or 4 numbers");
490 }
491 p.consume();
492 break;
493 }
494 SyntaxKind::NumberLiteral => {
495 count += 1;
496 p.consume();
497 }
498 SyntaxKind::Comma | SyntaxKind::Colon => {
499 p.error("Arguments of nine-slice need to be separated by spaces");
500 p.until(SyntaxKind::RParent);
501 break;
502 }
503 _ => {
504 p.error("Expected number literal or ')'");
505 p.until(SyntaxKind::RParent);
506 break;
507 }
508 }
509 }
510 if !p.expect(SyntaxKind::RParent) {
511 p.until(SyntaxKind::RParent);
512 }
513}