WIP: start working on translations: @tr()

This commit just do the parsing of the @tr macro.
This commit is contained in:
Olivier Goffart 2023-04-17 18:26:20 +02:00 committed by Olivier Goffart
parent 3a4f3c61d5
commit dcd8450dfa
17 changed files with 161 additions and 52 deletions

View file

@ -61,6 +61,7 @@ pub enum BuiltinFunction {
RegisterCustomFontByPath,
RegisterCustomFontByMemory,
RegisterBitmapFont,
Translate,
}
#[derive(Debug, Clone)]
@ -202,6 +203,10 @@ impl BuiltinFunction {
BuiltinFunction::RegisterBitmapFont => {
Type::Function { return_type: Box::new(Type::Void), args: vec![Type::Int32] }
}
BuiltinFunction::Translate => Type::Function {
return_type: Box::new(Type::String),
args: vec![Type::String, Type::Array(Type::String.into())],
},
}
}
@ -253,6 +258,7 @@ impl BuiltinFunction {
BuiltinFunction::RegisterCustomFontByPath
| BuiltinFunction::RegisterCustomFontByMemory
| BuiltinFunction::RegisterBitmapFont => false,
BuiltinFunction::Translate => false,
}
}
@ -297,6 +303,7 @@ impl BuiltinFunction {
BuiltinFunction::RegisterCustomFontByPath
| BuiltinFunction::RegisterCustomFontByMemory
| BuiltinFunction::RegisterBitmapFont => false,
BuiltinFunction::Translate => true,
}
}
}

View file

@ -2833,6 +2833,9 @@ fn compile_builtin_function_call(
panic!("internal error: invalid args to ImplicitLayoutInfo {:?}", arguments)
}
}
BuiltinFunction::Translate => {
format!("slint::private_api::translate({});", a.join(","))
}
}
}

View file

@ -2441,6 +2441,9 @@ fn compile_builtin_function_call(
let window_adapter_tokens = access_window_adapter_field(ctx);
quote!(slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).set_text_input_focused(#(#a)*))
}
BuiltinFunction::Translate => {
todo!("BuiltinFunction::Translate in rust")
}
}
}

View file

@ -106,6 +106,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
BuiltinFunction::DarkColorScheme => isize::MAX,
BuiltinFunction::SetTextInputFocused => PROPERTY_ACCESS_COST,
BuiltinFunction::TextInputFocused => PROPERTY_ACCESS_COST,
BuiltinFunction::Translate => 2 * ALLOC_COST + PROPERTY_ACCESS_COST,
}
}

View file

@ -359,7 +359,7 @@ declare_syntax! {
// FIXME: the test should test that as alternative rather than several of them (but it can also be a literal)
Expression-> [ ?Expression, ?FunctionCallExpression, ?IndexExpression, ?SelfAssignment,
?ConditionalExpression, ?QualifiedName, ?BinaryExpression, ?Array, ?ObjectLiteral,
?UnaryOpExpression, ?CodeBlock, ?StringTemplate, ?AtImageUrl, ?AtGradient,
?UnaryOpExpression, ?CodeBlock, ?StringTemplate, ?AtImageUrl, ?AtGradient, ?AtTr,
?MemberAccess ],
/// Concatenate the Expressions to make a string (usually expended from a template string)
StringTemplate -> [*Expression],
@ -367,6 +367,8 @@ declare_syntax! {
AtImageUrl -> [],
/// `@linear-gradient(...)` or `@radial-gradient(...)`
AtGradient -> [*Expression],
/// `@tr("foo", ...)` // the string is a StringLiteral
AtTr -> [*Expression],
/// expression()
FunctionCallExpression -> [*Expression],
/// `expression[index]`

View file

@ -212,6 +212,7 @@ fn parse_expression_helper(p: &mut impl Parser, precedence: OperatorPrecedence)
/// ```test
/// @image-url("/foo/bar.png")
/// @linear-gradient(0deg, blue, red)
/// @tr("foo", bar)
/// ```
fn parse_at_keyword(p: &mut impl Parser) {
debug_assert_eq!(p.peek().kind(), SyntaxKind::At);
@ -230,10 +231,13 @@ fn parse_at_keyword(p: &mut impl Parser) {
"radial-gradient" | "radial_gradient" => {
parse_gradient(p);
}
"tr" => {
parse_tr(p);
}
_ => {
p.consume();
p.test(SyntaxKind::Identifier); // consume the identifier, so that autocomplete works
p.error("Expected 'image-url', 'linear-gradient' or 'radial-gradient' after '@'");
p.error("Expected 'image-url', 'tr', 'linear-gradient' or 'radial-gradient' after '@'");
}
}
}
@ -351,3 +355,33 @@ fn parse_gradient(p: &mut impl Parser) {
p.test(SyntaxKind::Comma);
}
}
#[cfg_attr(test, parser_test)]
/// ```test,AtTr
/// @tr("foo")
/// @tr("foo", bar)
/// ```
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); //"tr"
p.expect(SyntaxKind::LParent);
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;
}
p.expect(SyntaxKind::StringLiteral);
while p.test(SyntaxKind::Comma) {
if !parse_expression(&mut *p) {
break;
}
}
p.expect(SyntaxKind::RParent);
}

View file

@ -241,6 +241,7 @@ impl Expression {
.map(|n| Self::from_expression_node(n, ctx))
.or_else(|| node.AtImageUrl().map(|n| Self::from_at_image_url_node(n, ctx)))
.or_else(|| node.AtGradient().map(|n| Self::from_at_gradient(n, ctx)))
.or_else(|| node.AtTr().map(|n| Self::from_at_tr(n, ctx)))
.or_else(|| {
node.QualifiedName().map(|n| {
let exp =
@ -515,6 +516,36 @@ impl Expression {
}
}
fn from_at_tr(node: syntax_nodes::AtTr, ctx: &mut LookupCtx) -> Expression {
let Some(string) = node
.child_text(SyntaxKind::StringLiteral)
.and_then(|s| crate::literals::unescape_string(&s))
else {
ctx.diag.push_error("Cannot parse string literal".into(), &node);
return Expression::Invalid;
};
let subs = node.Expression().map(|n| {
Expression::from_expression_node(n.clone(), ctx).maybe_convert_to(
Type::String,
&n,
ctx.diag,
)
});
Expression::FunctionCall {
function: Box::new(Expression::BuiltinFunctionReference(
BuiltinFunction::Translate,
Some(node.to_source_location()),
)),
arguments: vec![
Expression::StringLiteral(string),
Expression::Array { element_ty: Type::String, values: subs.collect() },
],
source_location: Some(node.to_source_location()),
}
}
/// Perform the lookup
fn from_qualified_name_node(
node: syntax_nodes::QualifiedName,

View file

@ -0,0 +1,23 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
export component X {
property <string> t1: @tr(boo);
// ^error{Expected plain string literal}
// ^^error{Syntax error: expected ';'}
// ^^^error{Parse error}
property <string> t2: @tr("boo\{t1}oo");
// ^error{Expected plain string literal}
// ^^error{Syntax error: expected ';'}
property <string> t3: @tr("boo" + "foo");
// ^error{Syntax error: expected '\)'}
// ^^error{Syntax error: expected ';'}
property <string> t4: @tr("foo{}", t1);
property <string> t4: @tr("foo{}", t1 t2);
// ^error{Syntax error: expected '\)'}
// ^^error{Syntax error: expected ';'}
}