1use 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 "keys" => {
262 parse_keys(p);
263 }
264 _ => {
265 p.consume();
266 p.test(SyntaxKind::Identifier); p.error("Expected 'image-url', 'tr', 'keys', 'conic-gradient', 'linear-gradient', or 'radial-gradient' after '@'");
268 }
269 }
270}
271
272#[cfg_attr(test, parser_test)]
273fn parse_array(p: &mut impl Parser) {
280 let mut p = p.start_node(SyntaxKind::Array);
281 p.expect(SyntaxKind::LBracket);
282
283 while p.nth(0).kind() != SyntaxKind::RBracket {
284 parse_expression(&mut *p);
285 if !p.test(SyntaxKind::Comma) {
286 break;
287 }
288 }
289 p.expect(SyntaxKind::RBracket);
290}
291
292#[cfg_attr(test, parser_test)]
293fn parse_object_notation(p: &mut impl Parser) {
300 let mut p = p.start_node(SyntaxKind::ObjectLiteral);
301 p.expect(SyntaxKind::LBrace);
302
303 while p.nth(0).kind() != SyntaxKind::RBrace {
304 let mut p = p.start_node(SyntaxKind::ObjectMember);
305 p.expect(SyntaxKind::Identifier);
306 p.expect(SyntaxKind::Colon);
307 parse_expression(&mut *p);
308 if !p.test(SyntaxKind::Comma) {
309 break;
310 }
311 }
312 p.expect(SyntaxKind::RBrace);
313}
314
315#[cfg_attr(test, parser_test)]
316fn parse_function_arguments(p: &mut impl Parser) {
323 p.expect(SyntaxKind::LParent);
324
325 while p.nth(0).kind() != SyntaxKind::RParent {
326 parse_expression(&mut *p);
327 if !p.test(SyntaxKind::Comma) {
328 break;
329 }
330 }
331 p.expect(SyntaxKind::RParent);
332}
333
334#[cfg_attr(test, parser_test)]
335fn parse_template_string(p: &mut impl Parser) {
340 let mut p = p.start_node(SyntaxKind::StringTemplate);
341 debug_assert!(p.nth(0).as_str().ends_with("\\{"));
342 p.expect(SyntaxKind::StringLiteral);
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 cont = peek.as_str().ends_with('{');
350 p.consume();
351 if !cont {
352 break;
353 }
354 }
355}
356
357#[cfg_attr(test, parser_test)]
358fn parse_gradient(p: &mut impl Parser) {
371 let mut p = p.start_node(SyntaxKind::AtGradient);
372 p.expect(SyntaxKind::At);
373 debug_assert!(p.peek().as_str().ends_with("gradient"));
374 p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
377
378 while !p.test(SyntaxKind::RParent) {
379 if !parse_expression(&mut *p) {
380 return;
381 }
382 p.test(SyntaxKind::Comma);
383 }
384}
385
386#[cfg_attr(test, parser_test)]
387fn parse_tr(p: &mut impl Parser) {
394 let mut p = p.start_node(SyntaxKind::AtTr);
395 p.expect(SyntaxKind::At);
396 debug_assert_eq!(p.peek().as_str(), "tr");
397 p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
399
400 let checkpoint = p.checkpoint();
401
402 fn consume_literal(p: &mut impl Parser) -> bool {
403 let peek = p.peek();
404 if peek.kind() != SyntaxKind::StringLiteral
405 || !peek.as_str().starts_with('"')
406 || !peek.as_str().ends_with('"')
407 {
408 p.error("Expected plain string literal");
409 return false;
410 }
411 p.expect(SyntaxKind::StringLiteral)
412 }
413
414 if !consume_literal(&mut *p) {
415 return;
416 }
417
418 if p.test(SyntaxKind::FatArrow) {
419 drop(p.start_node_at(checkpoint, SyntaxKind::TrContext));
420 if !consume_literal(&mut *p) {
421 return;
422 }
423 }
424
425 if p.peek().kind() == SyntaxKind::Pipe {
426 let mut p = p.start_node(SyntaxKind::TrPlural);
427 p.consume();
428 if !consume_literal(&mut *p) || !p.expect(SyntaxKind::Percent) {
429 let _ = p.start_node(SyntaxKind::Expression);
430 return;
431 }
432 parse_expression(&mut *p);
433 }
434
435 while p.test(SyntaxKind::Comma) {
436 if !parse_expression(&mut *p) {
437 break;
438 }
439 }
440 p.expect(SyntaxKind::RParent);
441}
442
443fn parse_markdown(p: &mut impl Parser) {
449 let mut p = p.start_node(SyntaxKind::AtMarkdown);
450 p.expect(SyntaxKind::At);
451 debug_assert!(p.peek().as_str().ends_with("markdown"));
452 p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
454
455 let mut has_content = false;
456 loop {
457 let peek = p.peek();
458 if peek.kind() != SyntaxKind::StringLiteral {
459 break;
460 }
461 if peek.as_str().ends_with('{') {
462 parse_template_string(&mut *p)
463 } else {
464 p.consume()
465 }
466 has_content = true;
467 }
468
469 if !has_content {
470 p.error("Expected string literal");
471 p.until(SyntaxKind::RParent);
472 return;
473 }
474
475 if !p.expect(SyntaxKind::RParent) {
476 p.until(SyntaxKind::RParent);
477 }
478}
479
480#[cfg_attr(test, parser_test)]
481fn parse_keys(p: &mut impl Parser) {
490 let mut p = p.start_node(SyntaxKind::AtKeys);
491 p.expect(SyntaxKind::At);
492 debug_assert_eq!(p.peek().as_str(), "keys");
493 p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
495
496 let mut key_count = 0_u32;
498
499 let mut alt_count = 0_u32;
500 let mut control_count = 0_u32;
501 let mut shift_count = 0_u32;
502 let mut meta_count = 0_u32;
503 let mut ignore_shift_count = 0_u32;
504 let mut ignore_alt_count = 0_u32;
505
506 #[derive(Eq, PartialEq)]
507 enum State {
508 Start,
509 NeedPlus,
510 NeedKey,
511 }
512 let mut state = State::Start;
513
514 fn bail(p: &mut crate::parser::Node<'_, impl Parser>, message: &str) {
515 p.error(message);
516 p.until(SyntaxKind::RParent);
517 }
518
519 loop {
520 match p.peek().kind() {
521 SyntaxKind::RParent => {
522 assert!(key_count <= 1);
523 if state == State::NeedKey {
525 p.error("Expected another identifier or string literal");
526 } else if key_count == 0
527 && (alt_count + control_count + shift_count + meta_count) > 0
528 {
529 p.error("A keyboard shortcut must be empty or contain exactly one key (with modifiers)");
530 }
531 p.consume();
532 break;
533 }
534 SyntaxKind::Plus => {
535 if state == State::NeedPlus {
536 state = State::NeedKey;
537 p.consume();
538 } else {
539 bail(
540 &mut p,
541 "Unexpected '+' in keyboard shortcut (use Plus to refer to the key)",
542 );
543 break;
544 }
545 continue;
546 }
547 SyntaxKind::Identifier | SyntaxKind::StringLiteral => {
548 if state == State::NeedPlus {
549 bail(&mut p, "Expected '+' to separate parts of a keyboard shortcut");
550 break;
551 }
552
553 let token = p.peek();
554 let mut consume_count = 1;
555 if token.kind() == SyntaxKind::Identifier {
557 let text = token.as_str();
558
559 let mut try_consume_question = || -> bool {
560 let next_token = p.nth(1);
561 if next_token.kind() == SyntaxKind::Question {
562 consume_count += 1;
563 true
564 } else {
565 false
566 }
567 };
568
569 match text {
570 "Ctrl" => {
571 bail(&mut p, "Ctrl is not in the Key namespace (Use Control instead)");
572 break;
573 }
574 "Control" => control_count += 1,
575 "Meta" => meta_count += 1,
576 "Alt" => {
577 if try_consume_question() {
578 ignore_alt_count += 1;
579 } else {
580 alt_count += 1
581 }
582 }
583 "Shift" => {
584 if try_consume_question() {
585 ignore_shift_count += 1;
586 } else {
587 shift_count += 1;
588 }
589 }
590 "AltR" | "ShiftR" | "MetaR" | "ControlR" => {
591 bail(&mut p, "Right-side modifiers are not supported");
592 break;
593 }
594 "AltGr" => {
595 bail(&mut p, "AltGr cannot be used as a modifier");
596 break;
597 }
598 "Command" | "Cmd" => {
599 bail(
600 &mut p,
601 &format!(
604 "{text} is not a cross-platform modifier\n\
605 Use cross-platform modifier names instead:\n\
606 \x20 ⌘ command -> Control\n\
607 \x20 ⌥ option -> Alt\n\
608 \x20 ^ control -> Meta\n\
609 \x20 ⇧ shift -> Shift"
610 ),
611 );
612 break;
613 }
614 "Win" | "Windows" => {
615 bail(
616 &mut p,
617 &format!(
618 "{text} is not a cross-platform modifier (Use `Meta` instead)"
619 ),
620 );
621 break;
622 }
623 _ => key_count += 1,
624 }
625 } else {
626 key_count += 1;
627 }
628
629 state = State::NeedPlus;
630
631 if [
632 alt_count,
633 control_count,
634 meta_count,
635 shift_count,
636 ignore_shift_count,
637 ignore_alt_count,
638 ]
639 .into_iter()
640 .max()
641 .unwrap_or_default()
642 > 1
643 {
644 bail(&mut p, "Duplicated modifier in keyboard shortcut");
645 break;
646 }
647 if shift_count > 0 && ignore_shift_count > 0 {
648 bail(&mut p, "Cannot use both Shift and Shift? (remove one of them)");
649 break;
650 }
651 if alt_count > 0 && ignore_alt_count > 0 {
652 bail(&mut p, "Cannot use both Alt and Alt? (remove one of them)");
653 break;
654 }
655 if key_count > 1 {
656 bail(&mut p, "A keyboard shortcut can only contain one key (with modifiers)");
657 break;
658 }
659
660 for _ in 0..consume_count {
661 p.consume();
662 }
663 continue;
664 }
665 _ => {
666 let hint = if state == State::NeedKey {
667 format!("\n(Consider using \"{}\")", p.peek().as_str())
668 } else {
669 "".into()
670 };
671 bail(
672 &mut p,
673 &format!(
674 "Expected '+', a string literal, or an identifier in the Keys namespace{hint}"
675 ),
676 );
677 break;
678 }
679 }
680 }
681}
682
683#[cfg_attr(test, parser_test)]
684fn parse_image_url(p: &mut impl Parser) {
691 let mut p = p.start_node(SyntaxKind::AtImageUrl);
692 p.consume(); p.consume(); if !(p.expect(SyntaxKind::LParent)) {
695 return;
696 }
697 let peek = p.peek();
698 if peek.kind() != SyntaxKind::StringLiteral {
699 p.error("@image-url must contain a plain path as a string literal");
700 p.until(SyntaxKind::RParent);
701 return;
702 }
703 if !peek.as_str().starts_with('"') || !peek.as_str().ends_with('"') {
704 p.error("@image-url must contain a plain path as a string literal, without any '\\{}' expressions");
705 p.until(SyntaxKind::RParent);
706 return;
707 }
708 p.expect(SyntaxKind::StringLiteral);
709 if !p.test(SyntaxKind::Comma) {
710 if !p.test(SyntaxKind::RParent) {
711 p.error("Expected ')' or ','");
712 p.until(SyntaxKind::RParent);
713 }
714 return;
715 }
716 if p.test(SyntaxKind::RParent) {
717 return;
718 }
719 if p.peek().as_str() != "nine-slice" {
720 p.error("Expected 'nine-slice(...)' argument");
721 p.until(SyntaxKind::RParent);
722 return;
723 }
724 p.consume();
725 if !p.expect(SyntaxKind::LParent) {
726 p.until(SyntaxKind::RParent);
727 return;
728 }
729 let mut count = 0;
730 loop {
731 match p.peek().kind() {
732 SyntaxKind::RParent => {
733 if count != 1 && count != 2 && count != 4 {
734 p.error("Expected 1 or 2 or 4 numbers");
735 }
736 p.consume();
737 break;
738 }
739 SyntaxKind::NumberLiteral => {
740 count += 1;
741 p.consume();
742 }
743 SyntaxKind::Comma | SyntaxKind::Colon => {
744 p.error("Arguments of nine-slice need to be separated by spaces");
745 p.until(SyntaxKind::RParent);
746 break;
747 }
748 _ => {
749 p.error("Expected number literal or ')'");
750 p.until(SyntaxKind::RParent);
751 break;
752 }
753 }
754 }
755 if !p.expect(SyntaxKind::RParent) {
756 p.until(SyntaxKind::RParent);
757 }
758}