Parse 9slice in @image-url

This commit is contained in:
Olivier Goffart 2024-02-09 10:52:08 +01:00
parent dd8fefd6a2
commit 6e2280ece3
3 changed files with 145 additions and 11 deletions

View file

@ -549,11 +549,24 @@ mod parser_trait {
/// consume everything until reaching a token of this kind
fn until(&mut self, kind: SyntaxKind) {
// FIXME! match {} () []
while {
let k = self.nth(0).kind();
k != kind && k != SyntaxKind::Eof
} {
let mut parens = 0;
let mut braces = 0;
let mut brackets = 0;
loop {
match self.nth(0).kind() {
k @ _ if k == kind && parens == 0 && braces == 0 && brackets == 0 => break,
SyntaxKind::Eof => break,
SyntaxKind::LParent => parens += 1,
SyntaxKind::LBrace => braces += 1,
SyntaxKind::LBracket => brackets += 1,
SyntaxKind::RParent if parens == 0 => break,
SyntaxKind::RParent => parens -= 1,
SyntaxKind::RBrace if braces == 0 => break,
SyntaxKind::RBrace => braces -= 1,
SyntaxKind::RBracket if brackets == 0 => break,
SyntaxKind::RBracket => brackets -= 1,
_ => {}
};
self.consume();
}
self.expect(kind);

View file

@ -224,12 +224,7 @@ 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" => {
let mut p = p.start_node(SyntaxKind::AtImageUrl);
p.consume(); // "@"
p.consume(); // "image-url"
p.expect(SyntaxKind::LParent);
p.expect(SyntaxKind::StringLiteral);
p.expect(SyntaxKind::RParent);
parse_image_url(p);
}
"linear-gradient" | "linear_gradient" => {
parse_gradient(p);
@ -417,3 +412,81 @@ fn parse_tr(p: &mut impl Parser) {
}
p.expect(SyntaxKind::RParent);
}
#[cfg_attr(test, parser_test)]
/// ```test,AtImageUrl
/// @image-url("foo.png")
/// @image-url("foo.png",)
/// @image-url("foo.png", 9slice(1 2 3 4))
/// @image-url("foo.png", 9slice(1))
/// ```
fn parse_image_url(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::AtImageUrl);
p.consume(); // "@"
p.consume(); // "image-url"
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() != "9slice" {
p.error("Expected '9slice(...)' 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 9slice 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);
return;
}
}

View file

@ -0,0 +1,48 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
export component SuperSimple {
property <image> i1: @image-url("hello.png");
property <string> path;
property <image> i2: @image-url(path);
// ^error{@image-url must contain a plain path as a string literal}
property <image> i3: @image-url("/home/\{path}.png");
// ^error{@image-url must contain a plain path as a string literal, without any '\\\{}' expressions}
property <image> i4: @image-url("/home/" + path + ".png");
// ^error{Expected '\)' or ','}
property <image> i5: @image-url(path + ".png");
// ^error{@image-url must contain a plain path as a string literal}
property <image> i6: @image-url;
// ^error{Syntax error: expected '\('}
property <image> i7: @image-url("foo", "bar");
// ^error{Expected '9slice\(...\)' argument}
property <image> i8: @image-url("foo", xyz(abc));
// ^error{Expected '9slice\(...\)' argument}
property <image> i9: @image-url("foo", 9slice(abc));
// ^error{Expected number literal or '\)'}
property <image> i10: @image-url("foo", 9slice(1 2 3));
// ^error{Expected 1 or 2 or 4 numbers}
property <image> i11: @image-url("foo", 9slice());
// ^error{Expected 1 or 2 or 4 numbers}
property <image> i12: @image-url("foo", 9slice(1 2 3 4 5));
// ^error{Expected 1 or 2 or 4 numbers}
property <image> i13: @image-url("foo", 9slice(1 2 foobar 4 5));
// ^error{Expected number literal or '\)'}
property <image> i14: @image-url("foo", 9slice);
// ^error{Syntax error: expected '\('}
property <image> i15: @image-url("foo", 9slice,);
// ^error{Syntax error: expected '\('}
property <image> i16: @image-url("foo", 9slice 42 42);
// ^error{Syntax error: expected '\('}
property <image> i17: @image-url("foo", 9slice(1px)); // error reported later
property <image> i18: @image-url("foo", 9slice(1%)); // error reported later
property <image> i19: @image-url("foo", 9slice(1, 2));
// ^error{Arguments of 9slice need to be separated by spaces}
property <image> i20: @image-url("foo", 9slice(2 + 3 ));
// ^error{Expected number literal or '\)'}
property <image> i21: @image-url("foo", 9slice(2 -3 ));
// ^error{Expected number literal or '\)'}
property <image> i22: @image-url("foo", 9slice(-2));
// ^error{Expected number literal or '\)'}
property <image> i22: @image-url("foo", 9slice(123456789));
}