use super::document::parse_qualified_name;
use super::prelude::*;
#[cfg_attr(test, parser_test)]
pub fn parse_expression(p: &mut impl Parser) -> bool {
p.peek(); parse_expression_helper(p, OperatorPrecedence::Default)
}
#[derive(Eq, PartialEq, Ord, PartialOrd)]
#[repr(u8)]
enum OperatorPrecedence {
Default,
Logical,
Equality,
Add,
Mul,
Unary,
}
fn parse_expression_helper(p: &mut impl Parser, precedence: OperatorPrecedence) -> bool {
let mut p = p.start_node(SyntaxKind::Expression);
let checkpoint = p.checkpoint();
let mut possible_range = false;
match p.nth(0).kind() {
SyntaxKind::Identifier => {
parse_qualified_name(&mut *p);
}
SyntaxKind::StringLiteral => {
if p.nth(0).as_str().ends_with('{') {
parse_template_string(&mut *p)
} else {
p.consume()
}
}
SyntaxKind::NumberLiteral => {
if p.nth(0).as_str().ends_with('.') {
possible_range = true;
}
p.consume()
}
SyntaxKind::ColorLiteral => p.consume(),
SyntaxKind::LParent => {
p.consume();
parse_expression(&mut *p);
p.expect(SyntaxKind::RParent);
}
SyntaxKind::LBracket => parse_array(&mut *p),
SyntaxKind::LBrace => parse_object_notation(&mut *p),
SyntaxKind::Plus | SyntaxKind::Minus | SyntaxKind::Bang => {
let mut p = p.start_node(SyntaxKind::UnaryOpExpression);
p.consume();
parse_expression_helper(&mut *p, OperatorPrecedence::Unary);
}
SyntaxKind::At => {
parse_at_keyword(&mut *p);
}
_ => {
p.error("invalid expression");
return false;
}
}
loop {
match p.nth(0).kind() {
SyntaxKind::Dot => {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::MemberAccess);
p.consume(); if possible_range && p.peek().kind() == SyntaxKind::NumberLiteral {
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()
);
p.error(error);
p.consume();
return false;
}
if !p.expect(SyntaxKind::Identifier) {
return false;
}
}
SyntaxKind::LParent => {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::FunctionCallExpression);
parse_function_arguments(&mut *p);
}
SyntaxKind::LBracket => {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::IndexExpression);
p.expect(SyntaxKind::LBracket);
parse_expression(&mut *p);
p.expect(SyntaxKind::RBracket);
}
_ => break,
}
possible_range = false;
}
if precedence >= OperatorPrecedence::Mul {
return true;
}
while matches!(p.nth(0).kind(), SyntaxKind::Star | SyntaxKind::Div) {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
p.consume();
parse_expression_helper(&mut *p, OperatorPrecedence::Mul);
}
if p.nth(0).kind() == SyntaxKind::Percent {
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");
p.consume();
return false;
}
if precedence >= OperatorPrecedence::Add {
return true;
}
while matches!(p.nth(0).kind(), SyntaxKind::Plus | SyntaxKind::Minus) {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
p.consume();
parse_expression_helper(&mut *p, OperatorPrecedence::Add);
}
if precedence > OperatorPrecedence::Equality {
return true;
}
if matches!(
p.nth(0).kind(),
SyntaxKind::LessEqual
| SyntaxKind::GreaterEqual
| SyntaxKind::EqualEqual
| SyntaxKind::NotEqual
| SyntaxKind::LAngle
| SyntaxKind::RAngle
) {
if precedence == OperatorPrecedence::Equality {
p.error("Use parentheses to disambiguate equality expression on the same level");
}
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
p.consume();
parse_expression_helper(&mut *p, OperatorPrecedence::Equality);
}
if precedence >= OperatorPrecedence::Logical {
return true;
}
let mut prev_logical_op = None;
while matches!(p.nth(0).kind(), SyntaxKind::AndAnd | SyntaxKind::OrOr) {
if let Some(prev) = prev_logical_op {
if prev != p.nth(0).kind() {
p.error("Use parentheses to disambiguate between && and ||");
prev_logical_op = None;
}
} else {
prev_logical_op = Some(p.nth(0).kind());
}
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::BinaryExpression);
p.consume();
parse_expression_helper(&mut *p, OperatorPrecedence::Logical);
}
if p.nth(0).kind() == SyntaxKind::Question {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint, SyntaxKind::ConditionalExpression);
p.consume();
parse_expression(&mut *p);
p.expect(SyntaxKind::Colon);
parse_expression(&mut *p);
}
true
}
#[cfg_attr(test, parser_test)]
fn parse_at_keyword(p: &mut impl Parser) {
debug_assert_eq!(p.peek().kind(), SyntaxKind::At);
match p.nth(1).as_str() {
"image-url" | "image_url" => {
parse_image_url(p);
}
"linear-gradient" | "linear_gradient" => {
parse_gradient(p);
}
"radial-gradient" | "radial_gradient" => {
parse_gradient(p);
}
"conic-gradient" | "conic_gradient" => {
parse_gradient(p);
}
"tr" => {
parse_tr(p);
}
"markdown" => {
parse_markdown(p);
}
"keys" => {
parse_keys(p);
}
_ => {
p.consume();
p.test(SyntaxKind::Identifier); p.error("Expected 'image-url', 'tr', 'keys', 'markdown' 'conic-gradient', 'linear-gradient', or 'radial-gradient' after '@'");
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_array(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::Array);
p.expect(SyntaxKind::LBracket);
while p.nth(0).kind() != SyntaxKind::RBracket {
parse_expression(&mut *p);
if !p.test(SyntaxKind::Comma) {
break;
}
}
p.expect(SyntaxKind::RBracket);
}
#[cfg_attr(test, parser_test)]
fn parse_object_notation(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::ObjectLiteral);
p.expect(SyntaxKind::LBrace);
while p.nth(0).kind() != SyntaxKind::RBrace {
let mut p = p.start_node(SyntaxKind::ObjectMember);
p.expect(SyntaxKind::Identifier);
p.expect(SyntaxKind::Colon);
parse_expression(&mut *p);
if !p.test(SyntaxKind::Comma) {
break;
}
}
p.expect(SyntaxKind::RBrace);
}
#[cfg_attr(test, parser_test)]
fn parse_function_arguments(p: &mut impl Parser) {
p.expect(SyntaxKind::LParent);
while p.nth(0).kind() != SyntaxKind::RParent {
parse_expression(&mut *p);
if !p.test(SyntaxKind::Comma) {
break;
}
}
p.expect(SyntaxKind::RParent);
}
#[cfg_attr(test, parser_test)]
fn parse_template_string(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::StringTemplate);
debug_assert!(p.nth(0).as_str().ends_with("\\{"));
p.expect(SyntaxKind::StringLiteral);
loop {
parse_expression(&mut *p);
let peek = p.peek();
if peek.kind != SyntaxKind::StringLiteral || !peek.as_str().starts_with('}') {
p.error("Error while parsing string template")
}
let cont = peek.as_str().ends_with('{');
p.consume();
if !cont {
break;
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_gradient(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::AtGradient);
p.expect(SyntaxKind::At);
debug_assert!(p.peek().as_str().ends_with("gradient"));
p.expect(SyntaxKind::Identifier);
p.expect(SyntaxKind::LParent);
while !p.test(SyntaxKind::RParent) {
if !parse_expression(&mut *p) {
return;
}
p.test(SyntaxKind::Comma);
}
}
#[cfg_attr(test, parser_test)]
fn parse_tr(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::AtTr);
p.expect(SyntaxKind::At);
debug_assert_eq!(p.peek().as_str(), "tr");
p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
let checkpoint = p.checkpoint();
fn consume_literal(p: &mut impl Parser) -> bool {
let peek = p.peek();
if peek.kind() != SyntaxKind::StringLiteral
|| !peek.as_str().starts_with('"')
|| !peek.as_str().ends_with('"')
{
p.error("Expected plain string literal");
return false;
}
p.expect(SyntaxKind::StringLiteral)
}
if !consume_literal(&mut *p) {
return;
}
if p.test(SyntaxKind::FatArrow) {
drop(p.start_node_at(checkpoint, SyntaxKind::TrContext));
if !consume_literal(&mut *p) {
return;
}
}
if p.peek().kind() == SyntaxKind::Pipe {
let mut p = p.start_node(SyntaxKind::TrPlural);
p.consume();
if !consume_literal(&mut *p) || !p.expect(SyntaxKind::Percent) {
let _ = p.start_node(SyntaxKind::Expression);
return;
}
parse_expression(&mut *p);
}
while p.test(SyntaxKind::Comma) {
if !parse_expression(&mut *p) {
break;
}
}
p.expect(SyntaxKind::RParent);
}
fn parse_markdown(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::AtMarkdown);
p.expect(SyntaxKind::At);
debug_assert!(p.peek().as_str().ends_with("markdown"));
p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
let mut has_content = false;
loop {
let peek = p.peek();
if peek.kind() != SyntaxKind::StringLiteral {
break;
}
if peek.as_str().ends_with('{') {
parse_template_string(&mut *p)
} else {
p.consume()
}
has_content = true;
}
if !has_content {
p.error("Expected string literal");
p.until(SyntaxKind::RParent);
return;
}
if !p.expect(SyntaxKind::RParent) {
p.until(SyntaxKind::RParent);
}
}
#[cfg_attr(test, parser_test)]
fn parse_keys(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::AtKeys);
p.expect(SyntaxKind::At);
debug_assert_eq!(p.peek().as_str(), "keys");
p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LParent);
let mut key_count = 0_u32;
let mut alt_count = 0_u32;
let mut control_count = 0_u32;
let mut shift_count = 0_u32;
let mut meta_count = 0_u32;
let mut ignore_shift_count = 0_u32;
let mut ignore_alt_count = 0_u32;
#[derive(Eq, PartialEq)]
enum State {
Start,
NeedPlus,
NeedKey,
}
let mut state = State::Start;
fn bail(p: &mut crate::parser::Node<'_, impl Parser>, message: &str) {
p.error(message);
p.until(SyntaxKind::RParent);
}
loop {
match p.peek().kind() {
SyntaxKind::RParent => {
assert!(key_count <= 1);
if state == State::NeedKey {
p.error("Expected another identifier or string literal");
} else if key_count == 0
&& (alt_count + control_count + shift_count + meta_count) > 0
{
p.error("A keyboard shortcut must be empty or contain exactly one key (with modifiers)");
}
p.consume();
break;
}
SyntaxKind::Plus => {
if state == State::NeedPlus {
state = State::NeedKey;
p.consume();
} else {
bail(
&mut p,
"Unexpected '+' in keyboard shortcut (use Plus to refer to the key)",
);
break;
}
continue;
}
SyntaxKind::Identifier | SyntaxKind::StringLiteral => {
if state == State::NeedPlus {
bail(&mut p, "Expected '+' to separate parts of a keyboard shortcut");
break;
}
let token = p.peek();
let mut consume_count = 1;
if token.kind() == SyntaxKind::Identifier {
let text = token.as_str();
let mut try_consume_question = || -> bool {
let next_token = p.nth(1);
if next_token.kind() == SyntaxKind::Question {
consume_count += 1;
true
} else {
false
}
};
match text {
"Ctrl" => {
bail(&mut p, "Ctrl is not in the Key namespace (Use Control instead)");
break;
}
"Control" => control_count += 1,
"Meta" => meta_count += 1,
"Alt" => {
if try_consume_question() {
ignore_alt_count += 1;
} else {
alt_count += 1
}
}
"Shift" => {
if try_consume_question() {
ignore_shift_count += 1;
} else {
shift_count += 1;
}
}
"AltR" | "ShiftR" | "MetaR" | "ControlR" => {
bail(&mut p, "Right-side modifiers are not supported");
break;
}
"AltGr" => {
bail(&mut p, "AltGr cannot be used as a modifier");
break;
}
"Command" | "Cmd" => {
bail(
&mut p,
&format!(
"{text} is not a cross-platform modifier\n\
Use cross-platform modifier names instead:\n\
\x20 ⌘ command -> Control\n\
\x20 ⌥ option -> Alt\n\
\x20 ^ control -> Meta\n\
\x20 ⇧ shift -> Shift"
),
);
break;
}
"Win" | "Windows" => {
bail(
&mut p,
&format!(
"{text} is not a cross-platform modifier (Use `Meta` instead)"
),
);
break;
}
_ => key_count += 1,
}
} else {
key_count += 1;
}
state = State::NeedPlus;
if [
alt_count,
control_count,
meta_count,
shift_count,
ignore_shift_count,
ignore_alt_count,
]
.into_iter()
.max()
.unwrap_or_default()
> 1
{
bail(&mut p, "Duplicated modifier in keyboard shortcut");
break;
}
if shift_count > 0 && ignore_shift_count > 0 {
bail(&mut p, "Cannot use both Shift and Shift? (remove one of them)");
break;
}
if alt_count > 0 && ignore_alt_count > 0 {
bail(&mut p, "Cannot use both Alt and Alt? (remove one of them)");
break;
}
if key_count > 1 {
bail(&mut p, "A keyboard shortcut can only contain one key (with modifiers)");
break;
}
for _ in 0..consume_count {
p.consume();
}
continue;
}
_ => {
let hint = if state == State::NeedKey {
format!("\n(Consider using \"{}\")", p.peek().as_str())
} else {
"".into()
};
bail(
&mut p,
&format!(
"Expected '+', a string literal, or an identifier in the Keys namespace{hint}"
),
);
break;
}
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_image_url(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::AtImageUrl);
p.consume(); p.consume(); if !(p.expect(SyntaxKind::LParent)) {
return;
}
let peek = p.peek();
if peek.kind() != SyntaxKind::StringLiteral {
p.error("@image-url must contain a plain path as a string literal");
p.until(SyntaxKind::RParent);
return;
}
if !peek.as_str().starts_with('"') || !peek.as_str().ends_with('"') {
p.error("@image-url must contain a plain path as a string literal, without any '\\{}' expressions");
p.until(SyntaxKind::RParent);
return;
}
p.expect(SyntaxKind::StringLiteral);
if !p.test(SyntaxKind::Comma) {
if !p.test(SyntaxKind::RParent) {
p.error("Expected ')' or ','");
p.until(SyntaxKind::RParent);
}
return;
}
if p.test(SyntaxKind::RParent) {
return;
}
if p.peek().as_str() != "nine-slice" {
p.error("Expected 'nine-slice(...)' argument");
p.until(SyntaxKind::RParent);
return;
}
p.consume();
if !p.expect(SyntaxKind::LParent) {
p.until(SyntaxKind::RParent);
return;
}
let mut count = 0;
loop {
match p.peek().kind() {
SyntaxKind::RParent => {
if count != 1 && count != 2 && count != 4 {
p.error("Expected 1 or 2 or 4 numbers");
}
p.consume();
break;
}
SyntaxKind::NumberLiteral => {
count += 1;
p.consume();
}
SyntaxKind::Comma | SyntaxKind::Colon => {
p.error("Arguments of nine-slice need to be separated by spaces");
p.until(SyntaxKind::RParent);
break;
}
_ => {
p.error("Expected number literal or ')'");
p.until(SyntaxKind::RParent);
break;
}
}
}
if !p.expect(SyntaxKind::RParent) {
p.until(SyntaxKind::RParent);
}
}