mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 02:39:28 +00:00
parent
4dd7d96a28
commit
9b71cf1a36
12 changed files with 226 additions and 25 deletions
|
@ -962,6 +962,12 @@ impl LookupObject for Expression {
|
|||
Type::Brush | Type::Color => ColorExpression(self).for_each_entry(ctx, f),
|
||||
Type::Image => ImageExpression(self).for_each_entry(ctx, f),
|
||||
Type::Array(_) => ArrayExpression(self).for_each_entry(ctx, f),
|
||||
Type::Float32 | Type::Int32 | Type::Percent => {
|
||||
NumberExpression(self).for_each_entry(ctx, f)
|
||||
}
|
||||
ty if ty.as_unit_product().is_some() => {
|
||||
NumValueExpression(self).for_each_entry(ctx, f)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
|
@ -981,6 +987,10 @@ impl LookupObject for Expression {
|
|||
Type::Brush | Type::Color => ColorExpression(self).lookup(ctx, name),
|
||||
Type::Image => ImageExpression(self).lookup(ctx, name),
|
||||
Type::Array(_) => ArrayExpression(self).lookup(ctx, name),
|
||||
Type::Float32 | Type::Int32 | Type::Percent => {
|
||||
NumberExpression(self).lookup(ctx, name)
|
||||
}
|
||||
ty if ty.as_unit_product().is_some() => NumValueExpression(self).lookup(ctx, name),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
|
@ -1106,3 +1116,77 @@ impl<'a> LookupObject for ArrayExpression<'a> {
|
|||
None.or_else(|| f("length", member_function(BuiltinFunction::ArrayLength)))
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression of type int or float
|
||||
struct NumberExpression<'a>(&'a Expression);
|
||||
impl<'a> LookupObject for NumberExpression<'a> {
|
||||
fn for_each_entry<R>(
|
||||
&self,
|
||||
ctx: &LookupCtx,
|
||||
f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
|
||||
) -> Option<R> {
|
||||
let member_function = |f: BuiltinFunction| {
|
||||
LookupResult::from(Expression::MemberFunction {
|
||||
base: Box::new(self.0.clone()),
|
||||
base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
|
||||
member: Box::new(Expression::BuiltinFunctionReference(
|
||||
f,
|
||||
ctx.current_token.as_ref().map(|t| t.to_source_location()),
|
||||
)),
|
||||
})
|
||||
};
|
||||
|
||||
None.or_else(|| f("round", member_function(BuiltinFunction::Round)))
|
||||
.or_else(|| f("ceil", member_function(BuiltinFunction::Ceil)))
|
||||
.or_else(|| f("floor", member_function(BuiltinFunction::Floor)))
|
||||
.or_else(|| f("sqrt", member_function(BuiltinFunction::Sqrt)))
|
||||
.or_else(|| f("asin", member_function(BuiltinFunction::ASin)))
|
||||
.or_else(|| f("acos", member_function(BuiltinFunction::ACos)))
|
||||
.or_else(|| f("atan", member_function(BuiltinFunction::ATan)))
|
||||
.or_else(|| f("log", member_function(BuiltinFunction::Log)))
|
||||
.or_else(|| f("pow", member_function(BuiltinFunction::Pow)))
|
||||
.or_else(|| NumValueExpression(self.0).for_each_entry(ctx, f))
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression of any numerical value with an unit
|
||||
struct NumValueExpression<'a>(&'a Expression);
|
||||
impl<'a> LookupObject for NumValueExpression<'a> {
|
||||
fn for_each_entry<R>(
|
||||
&self,
|
||||
ctx: &LookupCtx,
|
||||
f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
|
||||
) -> Option<R> {
|
||||
let member_macro = |f: BuiltinMacroFunction| {
|
||||
LookupResult::from(Expression::MemberFunction {
|
||||
base: Box::new(self.0.clone()),
|
||||
base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
|
||||
member: Box::new(Expression::BuiltinMacroReference(f, ctx.current_token.clone())),
|
||||
})
|
||||
};
|
||||
|
||||
None.or_else(|| f("mod", member_macro(BuiltinMacroFunction::Mod)))
|
||||
.or_else(|| f("clamp", member_macro(BuiltinMacroFunction::Clamp)))
|
||||
.or_else(|| f("abs", member_macro(BuiltinMacroFunction::Abs)))
|
||||
.or_else(|| f("max", member_macro(BuiltinMacroFunction::Max)))
|
||||
.or_else(|| f("min", member_macro(BuiltinMacroFunction::Min)))
|
||||
.or_else(|| {
|
||||
if self.0.ty() != Type::Angle {
|
||||
return None;
|
||||
}
|
||||
let member_function = |f: BuiltinFunction| {
|
||||
LookupResult::from(Expression::MemberFunction {
|
||||
base: Box::new(self.0.clone()),
|
||||
base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
|
||||
member: Box::new(Expression::BuiltinFunctionReference(
|
||||
f,
|
||||
ctx.current_token.as_ref().map(|t| t.to_source_location()),
|
||||
)),
|
||||
})
|
||||
};
|
||||
None.or_else(|| f("sin", member_function(BuiltinFunction::Sin)))
|
||||
.or_else(|| f("cos", member_function(BuiltinFunction::Cos)))
|
||||
.or_else(|| f("tan", member_function(BuiltinFunction::Tan)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use std::rc::Rc;
|
|||
use crate::diagnostics::BuildDiagnostics;
|
||||
use crate::expression_tree::{BuiltinFunction, Expression};
|
||||
use crate::object_tree::{visit_all_expressions, Component};
|
||||
use crate::parser::SyntaxKind;
|
||||
|
||||
/// Check the validity of expressions
|
||||
///
|
||||
|
@ -18,9 +19,13 @@ pub fn check_expressions(doc: &crate::object_tree::Document, diag: &mut BuildDia
|
|||
|
||||
fn check_expression(component: &Rc<Component>, e: &Expression, diag: &mut BuildDiagnostics) {
|
||||
match e {
|
||||
Expression::MemberFunction { .. } => {
|
||||
// Must already have been be reported.
|
||||
debug_assert!(diag.has_errors());
|
||||
Expression::MemberFunction { base_node, .. } => {
|
||||
if base_node.as_ref().is_some_and(|n| n.kind() == SyntaxKind::QualifiedName) {
|
||||
// Must already have been be reported in Expression::from_expression_node
|
||||
debug_assert!(diag.has_errors());
|
||||
} else {
|
||||
diag.push_error("Member function must be called".into(), base_node);
|
||||
}
|
||||
}
|
||||
Expression::BuiltinMacroReference(_, node) => {
|
||||
diag.push_error("Builtin function must be called".into(), node);
|
||||
|
|
|
@ -908,6 +908,15 @@ impl Expression {
|
|||
}
|
||||
Expression::MemberFunction { base, base_node, member } => {
|
||||
arguments.push((*base, base_node));
|
||||
if let Expression::BuiltinMacroReference(mac, n) = *member {
|
||||
arguments.extend(sub_expr);
|
||||
return crate::builtin_macros::lower_macro(
|
||||
mac,
|
||||
n,
|
||||
arguments.into_iter(),
|
||||
ctx.diag,
|
||||
);
|
||||
}
|
||||
adjust_arg_count = 1;
|
||||
member
|
||||
}
|
||||
|
|
|
@ -21,4 +21,18 @@ export component SuperSimple {
|
|||
// ^error{Cannot convert float to length}
|
||||
property <float> e: clamp(42.0, 23.0, 84.0, 32.0);
|
||||
// ^error{`clamp` needs three values}
|
||||
|
||||
property <float> f: ok1.clamp();
|
||||
// ^error{`clamp` needs three values}
|
||||
|
||||
property <float> g: ok1.clamp(1,2,3);
|
||||
// ^error{`clamp` needs three values}
|
||||
|
||||
property <float> h: ok1.clamp;
|
||||
// ^error{Member function must be called}
|
||||
|
||||
property <float> i: 42.0.clamp;
|
||||
// ^error{Member function must be called}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,11 @@ export component Foo {
|
|||
property <duration> m7: mod(5, 4ms);
|
||||
// ^error{Cannot convert float to duration}
|
||||
|
||||
property <duration> m8: (5).mod(4ms);
|
||||
// ^error{Cannot convert float to duration}
|
||||
|
||||
property <duration> m9: 5ms.mod(4);
|
||||
// ^error{Cannot convert float to duration}
|
||||
|
||||
property <float> a1: abs();
|
||||
// ^error{Needs 1 argument}
|
||||
|
@ -41,4 +46,18 @@ export component Foo {
|
|||
property <string> a5: abs(45px);
|
||||
// ^error{Cannot convert length to string}
|
||||
|
||||
property <string> a6: abs;
|
||||
// ^error{Builtin function must be called}
|
||||
|
||||
property <string> a7: (-21).abs;
|
||||
// ^error{Member function must be called}
|
||||
|
||||
|
||||
property <float> sq1: 1.0.sqrt(1);
|
||||
// ^error{The callback or function expects 0 arguments, but 1 are provided}
|
||||
|
||||
property <float> sq2: 1.0.sqrt;
|
||||
// ^error{Member function must be called}
|
||||
// ^^error{Cannot convert function\(float\) -> float to float}
|
||||
|
||||
}
|
|
@ -60,4 +60,7 @@ export X := Rectangle {
|
|||
// ^error{Cannot convert float to \[void\]}
|
||||
// ^^error{Cannot convert void to int}
|
||||
|
||||
property <int> to-float: "foobar".to-float;
|
||||
// ^error{Member function must be called}
|
||||
// ^^error{Cannot convert function\(string\) -> float to int}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,20 @@ export component TestCase {
|
|||
out property <float> t1: clamp(value, 10.0, 53.0);
|
||||
out property <float> t2: clamp(value, 43.0, 53.0);
|
||||
out property <float> t3: clamp(value, 10.0, 41.0);
|
||||
out property <float> s1: value.clamp(10.0, 53.0);
|
||||
out property <float> s2: value.clamp(43.0, 53.0);
|
||||
out property <float> s3: value.clamp(10.0, 41.0);
|
||||
|
||||
|
||||
r := Rectangle {
|
||||
property <int> max: 42;
|
||||
property <int> xx: Math.clamp(5, 2, 3) + max;
|
||||
}
|
||||
out property <bool> test: root.t1 == 42.0 && root.t2 == 43.0 && root.t3 == 41.0 && r.xx == 42 + 3;
|
||||
|
||||
out property <duration> dur: 45ms.clamp(0, 50ms);
|
||||
out property<bool> test_dur: dur == 5ms.clamp(45ms, 50ms);
|
||||
|
||||
out property <bool> test: root.t1 == 42.0 && root.t2 == 43.0 && root.t3 == 41.0 && r.xx == 42 + 3 && root.s1 == 42.0 && root.s2 == 43.0 && root.s3 == 41.0 && test_dur;
|
||||
}
|
||||
/*
|
||||
```cpp
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
TestCase := Rectangle {
|
||||
property<float> t1: cos(0);
|
||||
property<float> t2: cos(180deg);
|
||||
property<float> t3: cos(60deg);
|
||||
property<float> t4: cos(90deg);
|
||||
component TestCase inherits Window {
|
||||
out property<float> t1: cos(0);
|
||||
out property<float> t2: cos(180deg);
|
||||
out property<float> t3: cos(60deg);
|
||||
out property<float> t4: cos(90deg);
|
||||
|
||||
out property<bool> test: (0deg.cos() - 1.0).abs() < 0.00001 && 90deg.cos().abs() < 0.000001;
|
||||
}
|
||||
/*
|
||||
```cpp
|
||||
|
@ -15,6 +17,7 @@ assert(std::abs(instance.get_t1() - 1.0) < 0.0001);
|
|||
assert(std::abs(instance.get_t2() + 1.0) < 0.0001);
|
||||
assert(std::abs(instance.get_t3() - 0.5) < 0.0001);
|
||||
assert(std::abs(instance.get_t4()) < 0.0001);
|
||||
assert(instance.get_test());
|
||||
```
|
||||
|
||||
```rust
|
||||
|
@ -23,6 +26,7 @@ assert!((instance.get_t1() - 1.0).abs() < 0.0001);
|
|||
assert!((instance.get_t2() + 1.0).abs() < 0.0001);
|
||||
assert!((instance.get_t3() - 0.5).abs() < 0.0001);
|
||||
assert!((instance.get_t4()).abs() < 0.0001);
|
||||
assert!(instance.get_test());
|
||||
```
|
||||
|
||||
```js
|
||||
|
@ -31,5 +35,6 @@ assert(Math.abs(instance.t1 - 1) < Number.EPSILON);
|
|||
assert(Math.abs(instance.t2 - -1) < Number.EPSILON);
|
||||
assert(Math.abs(instance.t3 - 0.5) < Number.EPSILON);
|
||||
assert(Math.abs(instance.t4) < Number.EPSILON);
|
||||
assert(instance.test);
|
||||
```
|
||||
*/
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
export component TestCase {
|
||||
export component TestCase inherits Window {
|
||||
|
||||
in property <float> thousand: 1000;
|
||||
|
||||
out property <bool> test_sqrt: sqrt(100) == 10 && Math.sqrt(1) == 1 && sqrt(6.25) == 2.5 && abs(sqrt(thousand) - sqrt(1000)) < 0.00001;
|
||||
out property <bool> test_sqrt2: 100 .sqrt() == 10 && 1.0.sqrt() == 1 && 6.25.sqrt() == 2.5 && (thousand.sqrt() - (1000).sqrt()).abs() < 0.00001;
|
||||
out property <bool> test_abs: abs(100.5) == 100.5 && Math.abs(-200.5) == 200.5 && abs(0) == 0 && Math.abs(-thousand) == 1000;
|
||||
out property <bool> test_abs2: 100.5.abs() == 100.5 && (-200.5).abs() == 200.5 && 0 .abs() == 0 && (-thousand).abs() == 1000;
|
||||
out property <bool> test_log: log(4,2) == 2 && Math.log(9,3) == 2 && log(64,4) == 3;
|
||||
out property <bool> test_log2: 4 .log(2) == 2 && (9).log(3) == 2 && 64.0.log(4) == 3;
|
||||
out property <bool> test_pow: pow(4,2) == 16 && Math.pow(9,3) == 729 && pow(4,3) == 64 && abs(log(pow(thousand, 5), thousand) - 5) < 0.00001;
|
||||
out property <bool> test_pow2: 4..pow(2) == 16 && 9.0.pow(3) == 729 && (4).pow(3) == 64 && (thousand.pow(5).log(thousand) - 5).abs() < 0.00001;
|
||||
|
||||
out property <int> test_div_zero: 42 / 0;
|
||||
|
||||
out property <bool> test: test_sqrt && test_abs && test_log && test_pow && (test_div_zero) > -1;
|
||||
out property <bool> test2: test_sqrt2 && test_abs2 && test_log2 && test_pow2;
|
||||
out property <bool> test1: test_sqrt && test_abs && test_log && test_pow;
|
||||
|
||||
out property <bool> test: test1 && test2 && (test_div_zero) > -1;
|
||||
}
|
||||
/*
|
||||
```cpp
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
TestCase := Rectangle {
|
||||
property <int> a;
|
||||
property <float> t1: max(41, 12, min(100, 12), max(-10000, 0+98.5), -4) + min(a, 0.5);
|
||||
property <bool> t2: round(10/4) == 3 && floor(10/4) == 2 && ceil(10/4) == 3;
|
||||
component TestCase inherits Window {
|
||||
in property <int> a;
|
||||
out property <float> t1: max(41, 12, min(100, 12), max(-10000, 0+98.5), -4) + min(a, 0.5);
|
||||
out property <bool> t2: round(10/4) == 3 && floor(10/4) == 2 && ceil(10/4) == 3;
|
||||
|
||||
r := Rectangle {
|
||||
property <int> max: 42;
|
||||
property <int> xx: Math.max(1, 2, 3) + max;
|
||||
}
|
||||
property <bool> test: t2 && r.xx == 42 + 3;
|
||||
out property <bool> test: t2 && r.xx == 42 + 3 && 88px.max(5px, 45px) == 88px && 88ms.min(5ms, 45ms) == 5ms;
|
||||
}
|
||||
/*
|
||||
```cpp
|
||||
|
@ -18,6 +18,7 @@ auto handle = TestCase::create();
|
|||
const TestCase &instance = *handle;
|
||||
assert_eq(instance.get_t1(), 98.5);
|
||||
assert_eq(instance.get_t2(), true);
|
||||
assert(instance.get_test());
|
||||
```
|
||||
|
||||
|
||||
|
@ -25,11 +26,13 @@ assert_eq(instance.get_t2(), true);
|
|||
let instance = TestCase::new().unwrap();
|
||||
assert_eq!(instance.get_t1(), 98.5);
|
||||
assert_eq!(instance.get_t2(), true);
|
||||
assert!(instance.get_test());
|
||||
```
|
||||
|
||||
```js
|
||||
var instance = new slint.TestCase({});
|
||||
assert.equal(instance.t1, 98.5);
|
||||
assert(instance.t2);
|
||||
assert(instance.test);
|
||||
```
|
||||
*/
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
TestCase := Rectangle {
|
||||
property<int> t1: round(42.2);
|
||||
property<int> t2: round(23.5);
|
||||
property<int> t3: round(24.6);
|
||||
property<int> t4: round(25);
|
||||
component TestCase inherits Window {
|
||||
out property<int> t1: round(42.2);
|
||||
out property<int> t2: round(23.5);
|
||||
out property<int> t3: round(24.6);
|
||||
out property<int> t4: round(25);
|
||||
|
||||
property<int> n1: round(-42.2);
|
||||
property<int> n2: round(-23.5);
|
||||
property<int> n3: round(-24.6);
|
||||
property<int> n4: round(-25);
|
||||
out property<int> n1: round(-42.2);
|
||||
out property<int> n2: round(-23.5);
|
||||
out property<int> n3: round(-24.6);
|
||||
out property<int> n4: round(-25);
|
||||
|
||||
out property <bool> test: 188.9.round() == 189 && (-4.58).round() == -(5.1).round();
|
||||
}
|
||||
/*
|
||||
```cpp
|
||||
|
@ -24,6 +26,7 @@ assert_eq(instance.get_n1(), -42);
|
|||
assert_eq(instance.get_n2(), -24);
|
||||
assert_eq(instance.get_n3(), -25);
|
||||
assert_eq(instance.get_n4(), -25);
|
||||
assert(instance.get_test());
|
||||
```
|
||||
|
||||
```rust
|
||||
|
@ -36,6 +39,7 @@ assert_eq!(instance.get_n1(), -42);
|
|||
assert_eq!(instance.get_n2(), -24);
|
||||
assert_eq!(instance.get_n3(), -25);
|
||||
assert_eq!(instance.get_n4(), -25);
|
||||
assert!(instance.get_test());
|
||||
```
|
||||
|
||||
```js
|
||||
|
@ -48,5 +52,6 @@ assert.equal(instance.n1, -42);
|
|||
assert.equal(instance.n2, -24);
|
||||
assert.equal(instance.n3, -25);
|
||||
assert.equal(instance.n4, -25);
|
||||
assert(instance.test);
|
||||
```
|
||||
*/
|
||||
|
|
|
@ -150,6 +150,9 @@ fn format_node(
|
|||
SyntaxKind::PropertyChangedCallback => {
|
||||
return format_property_changed_callback(node, writer, state);
|
||||
}
|
||||
SyntaxKind::MemberAccess => {
|
||||
return format_member_access(node, writer, state);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
@ -1233,10 +1236,34 @@ fn format_property_changed_callback(
|
|||
&& whitespace_to(&mut sub, SyntaxKind::DeclaredIdentifier, writer, state, " ")?
|
||||
&& whitespace_to(&mut sub, SyntaxKind::FatArrow, writer, state, " ")?
|
||||
&& whitespace_to(&mut sub, SyntaxKind::CodeBlock, writer, state, " ")?;
|
||||
for n in sub {
|
||||
fold(n, writer, state)?;
|
||||
}
|
||||
state.new_line();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_member_access(
|
||||
node: &SyntaxNode,
|
||||
writer: &mut impl TokenWriter,
|
||||
state: &mut FormatState,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let n = syntax_nodes::MemberAccess::from(node.clone());
|
||||
// Special case fo things like `42 .mod(x)` where a space is needed otherwise it lexes differently
|
||||
let need_space = n.Expression().child_token(SyntaxKind::NumberLiteral).is_some_and(|nl| {
|
||||
!nl.text().contains('.') && nl.text().chars().last().is_some_and(|c| c.is_numeric())
|
||||
});
|
||||
let space_before_dot = if need_space { " " } else { "" };
|
||||
let mut sub = n.children_with_tokens();
|
||||
let _ok = whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, "")?
|
||||
&& whitespace_to(&mut sub, SyntaxKind::Dot, writer, state, space_before_dot)?
|
||||
&& whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?;
|
||||
for n in sub {
|
||||
fold(n, writer, state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -2027,6 +2054,18 @@ export component MainWindow2 inherits Rectangle {
|
|||
y += 1;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_member() {
|
||||
assert_formatting(
|
||||
"component X { expr: 42 .log(x) + 41 . log(y) + foo . bar + 21.0.log(0) + 54. .log(8) ; x: 42px.max(42px . min (0.px)); }",
|
||||
r#"component X {
|
||||
expr: 42 .log(x) + 41 .log(y) + foo.bar + 21.0.log(0) + 54..log(8);
|
||||
x: 42px.max(42px.min(0.px));
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue