Make mod() a macro that can take generic types instead of just integer

This commit is contained in:
Olivier Goffart 2022-07-11 16:54:17 +02:00 committed by Olivier Goffart
parent d48c590346
commit 92b4f52556
9 changed files with 78 additions and 15 deletions

View file

@ -3,6 +3,9 @@ All notable changes to this project are documented in this file.
## Unreleased
### Changed
- `mod` now works on any numeric type, not only integers.
### Added
- `Button`: Add a `checkable` property that turns the button into a toggle

View file

@ -1067,9 +1067,9 @@ These functions are available both in the global scope and in the `Math` namespa
Return the arguments with the minimum (or maximum) value. All arguments must be of the same numeric type
* **`mod(int, int) -> int`**
* **`mod(T, T) -> T`**
Perform a modulo operation.
Perform a modulo operation, where T is some numeric type.
* **`abs(float) -> float`**

View file

@ -24,6 +24,7 @@ pub fn lower_macro(
match mac {
BuiltinMacroFunction::Min => min_max_macro(n, '<', sub_expr.collect(), diag),
BuiltinMacroFunction::Max => min_max_macro(n, '>', sub_expr.collect(), diag),
BuiltinMacroFunction::Mod => mod_macro(n, sub_expr.collect(), diag),
BuiltinMacroFunction::Debug => debug_macro(n, sub_expr.collect(), diag),
BuiltinMacroFunction::CubicBezier => {
let mut has_error = None;
@ -90,6 +91,51 @@ fn min_max_macro(
base
}
fn mod_macro(
node: Option<NodeOrToken>,
args: Vec<(Expression, Option<NodeOrToken>)>,
diag: &mut BuildDiagnostics,
) -> Expression {
if args.len() != 2 {
diag.push_error("Needs 2 arguments".into(), &node);
return Expression::Invalid;
}
let (lhs_ty, rhs_ty) = (args[0].0.ty(), args[1].0.ty());
let common_ty = if lhs_ty.default_unit().is_some() {
lhs_ty
} else if rhs_ty.default_unit().is_some() {
rhs_ty
} else if matches!(lhs_ty, Type::UnitProduct(_)) {
lhs_ty
} else if matches!(rhs_ty, Type::UnitProduct(_)) {
rhs_ty
} else {
Type::Float32
};
let source_location = node.map(|n| n.to_source_location());
let function = Box::new(Expression::BuiltinFunctionReference(
BuiltinFunction::Mod,
source_location.clone(),
));
let arguments = args.into_iter().map(|(e, n)| e.maybe_convert_to(common_ty.clone(), &n, diag));
if matches!(common_ty, Type::Float32) {
Expression::FunctionCall { function, arguments: arguments.collect(), source_location }
} else {
Expression::Cast {
from: Expression::FunctionCall {
function,
arguments: arguments
.map(|a| Expression::Cast { from: a.into(), to: Type::Float32 })
.collect(),
source_location,
}
.into(),
to: common_ty.clone(),
}
}
}
fn rgb_macro(
node: Option<NodeOrToken>,
args: Vec<(Expression, Option<NodeOrToken>)>,

View file

@ -53,11 +53,22 @@ pub enum BuiltinFunction {
#[derive(Debug, Clone)]
/// A builtin function which is handled by the compiler pass
///
/// Builtin function expect their arguments in one and a specific type, so that's easier
/// for the generator. Macro however can do some transformation on their argument.
///
pub enum BuiltinMacroFunction {
/// Transform `min(a, b, c, ..., z)` into a series of conditional expression and comparisons
Min,
/// Transform `max(a, b, c, ..., z)` into a series of conditional expression and comparisons
Max,
/// Add the right conversion operations so that the return type is the same as the argument type
Mod,
CubicBezier,
/// The argument can be r,g,b,a or r,g,b and they can be percentages or integer.
/// transform the argument so it is always rgb(r, g, b, a) with r, g, b between 0 and 255.
Rgb,
/// transform `debug(a, b, c)` into debug `a + " " + b + " " + c`
Debug,
}

View file

@ -2363,11 +2363,7 @@ fn compile_builtin_function_call(
BuiltinFunction::Debug => {
format!("std::cout << {} << std::endl;", a.join("<<"))
}
BuiltinFunction::Mod => format!(
"static_cast<int>({}) % static_cast<int>({})",
a.next().unwrap(),
a.next().unwrap()
),
BuiltinFunction::Mod => format!("std::fmod({}, {})", a.next().unwrap(), a.next().unwrap()),
BuiltinFunction::Round => format!("std::round({})", a.next().unwrap()),
BuiltinFunction::Ceil => format!("std::ceil({})", a.next().unwrap()),
BuiltinFunction::Floor => format!("std::floor({})", a.next().unwrap()),

View file

@ -2072,7 +2072,7 @@ fn compile_builtin_function_call(
}
BuiltinFunction::AnimationTick => quote!(slint::re_exports::current_tick().0),
BuiltinFunction::Debug => quote!(slint::internal::debug(#(#a)*)),
BuiltinFunction::Mod => quote!((#(#a as i32)%*)),
BuiltinFunction::Mod => quote!((#(#a as f64)%*)),
BuiltinFunction::Round => quote!((#(#a)* as f64).round()),
BuiltinFunction::Ceil => quote!((#(#a)* as f64).ceil()),
BuiltinFunction::Floor => quote!((#(#a)* as f64).floor()),

View file

@ -523,7 +523,7 @@ impl LookupObject for MathFunctions {
let t = &ctx.current_token;
let sl = || t.as_ref().map(|t| t.to_source_location());
let mut f = |n, e: Expression| f(n, e.into());
None.or_else(|| f("mod", BuiltinFunctionReference(BuiltinFunction::Mod, sl())))
None.or_else(|| f("mod", BuiltinMacroReference(BuiltinMacroFunction::Mod, t.clone())))
.or_else(|| f("round", BuiltinFunctionReference(BuiltinFunction::Round, sl())))
.or_else(|| f("ceil", BuiltinFunctionReference(BuiltinFunction::Ceil, sl())))
.or_else(|| f("floor", BuiltinFunctionReference(BuiltinFunction::Floor, sl())))

View file

@ -230,8 +230,8 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
Value::Void
}
Expression::BuiltinFunctionReference(BuiltinFunction::Mod, _) => {
let mut to_int = |e| -> i32 { eval_expression(e, local_context).try_into().unwrap() };
Value::Number((to_int(&arguments[0]) % to_int(&arguments[1])) as _)
let mut to_num = |e| -> f64 { eval_expression(e, local_context).try_into().unwrap() };
Value::Number(to_num(&arguments[0]) % to_num(&arguments[1]))
}
Expression::BuiltinFunctionReference(BuiltinFunction::Round, _) => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();

View file

@ -3,30 +3,37 @@
TestCase := Rectangle {
property<int> t1: mod(42, 2);
property<float> t2: mod(8.3, 10);
property<float> t2: mod(18.5, 10);
property<int> t3: mod(153, 10);
property <duration> t4: mod(5432ms, 1s);
property <bool> test: t1 == 0 && t2 == 8.5 && t3 == 3 && t4 == 432ms;
}
/*
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
assert_eq(instance.get_t1(), 0);
assert_eq(instance.get_t2(), 8.0);
assert_eq(instance.get_t2(), 8.5);
assert_eq(instance.get_t3(),3);
assert_eq(instance.get_t4(),432);
```
```rust
let instance = TestCase::new();
assert_eq!(instance.get_t1(), 0);
assert_eq!(instance.get_t2(), 8.0);
assert_eq!(instance.get_t2(), 8.5);
assert_eq!(instance.get_t3(), 3);
assert_eq!(instance.get_t4(),432);
```
```js
var instance = new slint.TestCase({});
assert.equal(instance.t1, 0);
assert.equal(instance.t2, 8.0);
assert.equal(instance.t2, 8.5);
assert.equal(instance.t3, 3);
assert.equal(instance.t4, 432);
```
*/