Skip to content

Commit

Permalink
Math postfix member functions on numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
ogoffart committed Aug 15, 2024
1 parent 4dd7d96 commit 9b71cf1
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 25 deletions.
84 changes: 84 additions & 0 deletions internal/compiler/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
Expand All @@ -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,
},
}
Expand Down Expand Up @@ -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)))
})
}
}
11 changes: 8 additions & 3 deletions internal/compiler/passes/check_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand All @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions internal/compiler/passes/resolving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
14 changes: 14 additions & 0 deletions internal/compiler/tests/syntax/expressions/clamp.slint
Original file line number Diff line number Diff line change
Expand Up @@ -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}


}
19 changes: 19 additions & 0 deletions internal/compiler/tests/syntax/expressions/math-macro.slint
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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}

}
3 changes: 3 additions & 0 deletions internal/compiler/tests/syntax/lookup/conversion.slint
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
10 changes: 9 additions & 1 deletion tests/cases/expr/clamp.slint
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 10 additions & 5 deletions tests/cases/expr/cos.slint
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
```
*/
11 changes: 9 additions & 2 deletions tests/cases/expr/math.slint
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 8 additions & 5 deletions tests/cases/expr/minmax.slint
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
// 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
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());
```
```rust
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);
```
*/
23 changes: 14 additions & 9 deletions tests/cases/expr/round.slint
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
```
*/
Loading

0 comments on commit 9b71cf1

Please sign in to comment.