Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions api/cpp/include/private/slint_models.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,36 @@ long int model_length(const std::shared_ptr<M> &model)
}
}

template<typename M, typename P>
bool model_any(const std::shared_ptr<M> &model, P predicate)
{
long int count = model_length(model);

for (long int i = 0; i < count; ++i) {
auto data = access_array_index(model, i);
if (predicate(data)) {
return true;
}
}

return false;
}

template<typename M, typename P>
bool model_all(const std::shared_ptr<M> &model, P predicate)
{
long int count = model_length(model);

for (long int i = 0; i < count; ++i) {
auto data = access_array_index(model, i);
if (!predicate(data)) {
return false;
}
}

return true;
}

} // namespace private_api

/// A Model is providing Data for Slint Models or ListView elements of the
Expand Down
3 changes: 2 additions & 1 deletion api/node/rust/interpreter/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@ pub fn to_value(
| Type::PathData
| Type::LayoutCache
| Type::ArrayOfU16
| Type::ElementReference => Err(napi::Error::from_reason("reason")),
| Type::ElementReference
| Type::Closure => Err(napi::Error::from_reason("reason")),
Type::StyledText => {
let obj = unknown.coerce_to_object()?;
let styled_instance: ClassInstance<SlintStyledText> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,21 @@ Arrays define the following operations:

- **`array.length`**: One can query the length of an array and model using the builtin `.length` property.
- **`array[index]`**: The index operator retrieves individual elements of an array.
- **`array.any(name => condition)`**: Returns true if the boolean predicate is true for at least one element.
- **`array.all(name => condition)`**: Returns true if the boolean predicate is true for every element.

Out of bound access into an array will return default-constructed values.
The argument name of an `any` or `all` predicate is available only in the predicate expression,
and its type is inferred from the array element type. `any` returns false for an empty array,
while `all` returns true for an empty array.

```slint
export component Example {
in-out property<[int]> list-of-int: [1,2,3];

out property <int> list-len: list-of-int.length;
out property <int> first-int: list-of-int[0];
out property <bool> contains-two: list-of-int.any(value => value == 2);
out property <bool> all-positive: list-of-int.all(value => value > 0);
}
```

11 changes: 11 additions & 0 deletions editors/tree-sitter-slint/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ module.exports = grammar({
$.simple_identifier,
$.function_call,
$.member_access,
$.closure_expression,
$.unary_expression,
$.binary_expression,
$.ternary_expression,
Expand All @@ -345,6 +346,16 @@ module.exports = grammar({

parens_op: ($) => seq("(", field("left", $.expression), ")"),

closure_expression: ($) =>
prec.right(
2,
seq(
field("argument", $.simple_identifier),
"=>",
field("body", $.expression),
),
),

index_op: ($) =>
prec(
18,
Expand Down
3 changes: 2 additions & 1 deletion internal/compiler/builtin_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,8 @@ fn to_debug_string(
| Type::LayoutCache
| Type::ArrayOfU16
| Type::Model
| Type::PathData => {
| Type::PathData
| Type::Closure => {
diag.push_error("Cannot debug this expression".into(), node);
Expression::Invalid
}
Expand Down
21 changes: 21 additions & 0 deletions internal/compiler/expression_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub enum BuiltinFunction {
ColorWithAlpha,
ImageSize,
ArrayLength,
ArrayAny,
ArrayAll,
Rgb,
Hsv,
Oklch,
Expand Down Expand Up @@ -273,6 +275,8 @@ declare_builtin_function_types!(
name: crate::langtype::BuiltinStruct::Size.into(),
})),
ArrayLength: (Type::Model) -> Type::Int32,
ArrayAny: (Type::Model, Type::Closure) -> Type::Bool,
ArrayAll: (Type::Model, Type::Closure) -> Type::Bool,
Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color,
Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
Oklch: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
Expand Down Expand Up @@ -424,6 +428,7 @@ impl BuiltinFunction {
BuiltinFunction::ColorToStyledText => true,
BuiltinFunction::OpenUrl => false,
BuiltinFunction::MacosBringAllWindowsToFront => false,
BuiltinFunction::ArrayAny | BuiltinFunction::ArrayAll => true,
}
}

Expand Down Expand Up @@ -512,6 +517,7 @@ impl BuiltinFunction {
BuiltinFunction::ColorToStyledText => true,
BuiltinFunction::OpenUrl => false,
BuiltinFunction::MacosBringAllWindowsToFront => false,
BuiltinFunction::ArrayAny | BuiltinFunction::ArrayAll => true,
}
}
}
Expand Down Expand Up @@ -907,6 +913,11 @@ pub enum Expression {
},

EmptyComponentFactory,

Closure {
arg_name: SmolStr,
expression: Box<Expression>,
},
}

impl Expression {
Expand Down Expand Up @@ -1033,6 +1044,7 @@ impl Expression {
Expression::MinMax { ty, .. } => ty.clone(),
Expression::EmptyComponentFactory => Type::ComponentFactory,
Expression::DebugHook { expression, .. } => expression.ty(),
Expression::Closure { .. } => Type::Closure,
}
}

Expand Down Expand Up @@ -1167,6 +1179,7 @@ impl Expression {
}
Expression::EmptyComponentFactory => {}
Expression::DebugHook { expression, .. } => visitor(expression),
Expression::Closure { expression, .. } => visitor(expression),
}
}

Expand Down Expand Up @@ -1303,6 +1316,7 @@ impl Expression {
}
Expression::EmptyComponentFactory => {}
Expression::DebugHook { expression, .. } => visitor(expression),
Expression::Closure { expression, .. } => visitor(expression),
}
}

Expand Down Expand Up @@ -1407,6 +1421,7 @@ impl Expression {
Expression::MinMax { lhs, rhs, .. } => lhs.is_constant(ga) && rhs.is_constant(ga),
Expression::EmptyComponentFactory => true,
Expression::DebugHook { .. } => false,
Expression::Closure { expression, .. } => expression.is_constant(ga),
}
}

Expand Down Expand Up @@ -1658,6 +1673,7 @@ impl Expression {
arguments: vec![Self::default_value_for_type(&Type::String)],
source_location: None,
},
Type::Closure => Expression::Invalid,
}
}

Expand Down Expand Up @@ -2192,5 +2208,10 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std
pretty_print(f, expression)?;
write!(f, "\"{id}\")")
}
Expression::Closure { arg_name, expression } => {
let display_name = arg_name.strip_prefix("local_").unwrap_or(arg_name);
write!(f, "{display_name} => ")?;
pretty_print(f, expression)
}
}
}
12 changes: 12 additions & 0 deletions internal/compiler/generator/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4451,6 +4451,12 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
),
}
}
Expression::Closure { arg_name, expression } => {
let arg = ident(arg_name);
let expr = compile_expression(expression, ctx);

format!("[&](auto const &{arg}) -> bool {{ return {expr}; }}")
}
}
}

Expand Down Expand Up @@ -5070,6 +5076,12 @@ fn compile_builtin_function_call(
let color = a.next().unwrap();
format!("slint::private_api::color_to_styled_text({})", color)
}
BuiltinFunction::ArrayAny => {
format!("slint::private_api::model_any({}, {})", a.next().unwrap(), a.next().unwrap())
},
BuiltinFunction::ArrayAll => {
format!("slint::private_api::model_all({}, {})", a.next().unwrap(), a.next().unwrap())
},
}
}

Expand Down
39 changes: 37 additions & 2 deletions internal/compiler/generator/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2942,8 +2942,12 @@ fn access_item_rc(pr: &llr::MemberReference, ctx: &EvaluationContext) -> TokenSt
/// Compile `expr` to a Rust expression returning an owned value.
fn compile_expression_to_value(expr: &Expression, ctx: &EvaluationContext) -> TokenStream {
let compiled_expr = compile_expression(expr, ctx);

quote!((#compiled_expr).clone())
// Closures compile to closures, which aren't `Clone` and don't need to be cloned.
if matches!(expr, Expression::Closure { .. }) {
compiled_expr
} else {
quote!((#compiled_expr).clone())
}
}

/// Compile `expr` to a Rust expression which may potentially return a reference.
Expand Down Expand Up @@ -3563,6 +3567,13 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
}
}
}
Expression::Closure { arg_name, expression } => {
let arg_name = ident(arg_name);
let expression = compile_expression(expression, ctx);
quote! {
|#arg_name| {#expression}
}
}
}
}

Expand Down Expand Up @@ -4322,6 +4333,30 @@ fn compile_builtin_function_call(
let color = a.next().unwrap();
quote!(sp::color_to_styled_text(#color))
}
BuiltinFunction::ArrayAny => {
let arr_expression = compile_expression_to_value(&arguments[0], ctx);
let Expression::Closure { arg_name, expression } = &arguments[1] else {
panic!("internal error: ArrayAny expects a closure as second argument")
};
let arg_name = ident(arg_name);
let closure_expression = compile_expression(expression, ctx);
quote!({
let arr = #arr_expression;
sp::model_any(&arr, |#arg_name| -> bool { #closure_expression })
})
Comment on lines +4344 to +4346

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try to keep as much code as possible out of the generated code and in core to improve compile times.

So please make the any and all functions functions in i-slint-core and use them here (like you do for C++).

}
BuiltinFunction::ArrayAll => {
let arr_expression = compile_expression_to_value(&arguments[0], ctx);
let Expression::Closure { arg_name, expression } = &arguments[1] else {
panic!("internal error: ArrayAll expects a closure as second argument")
};
let arg_name = ident(arg_name);
let closure_expression = compile_expression(expression, ctx);
quote!({
let arr = #arr_expression;
sp::model_all(&arr, |#arg_name| -> bool { #closure_expression })
})
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions internal/compiler/langtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub enum Type {
ArrayOfU16,

StyledText,
Closure,
}

impl core::cmp::PartialEq for Type {
Expand Down Expand Up @@ -116,6 +117,7 @@ impl core::cmp::PartialEq for Type {
Type::ArrayOfU16 => matches!(other, Type::ArrayOfU16),
Type::StyledText => matches!(other, Type::StyledText),
Type::DataTransfer => matches!(other, Type::DataTransfer),
Type::Closure => matches!(other, Type::Closure),
}
}
}
Expand Down Expand Up @@ -194,6 +196,7 @@ impl Display for Type {
Type::LayoutCache => write!(f, "layout cache"),
Type::ArrayOfU16 => write!(f, "[u16]"),
Type::StyledText => write!(f, "styled-text"),
Type::Closure => write!(f, "closure"),
}
}
}
Expand Down Expand Up @@ -337,6 +340,7 @@ impl Type {
Type::LayoutCache => None,
Type::ArrayOfU16 => None,
Type::StyledText => None,
Type::Closure => None,
}
}

Expand Down
12 changes: 11 additions & 1 deletion internal/compiler/llr/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ pub enum Expression {
/// The `n` value to use for the plural form if it is a plural form
plural: Option<Box<Expression>>,
},

Closure {
arg_name: SmolStr,
expression: Box<Expression>,
},
}

impl Expression {
Expand All @@ -287,7 +292,8 @@ impl Expression {
| Type::InferredCallback
| Type::ElementReference
| Type::LayoutCache
| Type::ArrayOfU16 => return None,
| Type::ArrayOfU16
| Type::Closure => return None,
Type::Float32
| Type::Duration
| Type::Int32
Expand Down Expand Up @@ -400,6 +406,7 @@ impl Expression {
Self::EmptyComponentFactory => Type::ComponentFactory,
Self::EmptyDataTransfer => Type::DataTransfer,
Self::TranslationReference { .. } => Type::String,
Self::Closure { .. } => Type::Closure,
}
}
}
Expand Down Expand Up @@ -531,6 +538,9 @@ macro_rules! visit_impl {
$visitor(plural);
}
}
Expression::Closure { expression, .. } => {
$visitor(expression);
}
}
};
}
Expand Down
4 changes: 4 additions & 0 deletions internal/compiler/llr/lower_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ pub fn lower_expression(
tree_Expression::EmptyComponentFactory => llr_Expression::EmptyComponentFactory,
tree_Expression::EmptyDataTransfer => llr_Expression::EmptyDataTransfer,
tree_Expression::DebugHook { expression, .. } => lower_expression(expression, ctx),
tree_Expression::Closure { arg_name, expression } => llr_Expression::Closure {
arg_name: arg_name.clone(),
expression: Box::new(lower_expression(expression, ctx)),
},
}
}

Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/llr/optim_passes/inline_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ fn expression_cost(exp: &Expression, ctx: &EvaluationContext) -> isize {
Expression::EmptyComponentFactory => 10,
Expression::EmptyDataTransfer => 10,
Expression::TranslationReference { .. } => PROPERTY_ACCESS_COST + 2 * ALLOC_COST,
// The body cost is added by the visit() walk below; returning the body
// cost here would double-count it.
Expression::Closure { .. } => 0,
};

exp.visit(|e| cost = cost.saturating_add(expression_cost(e, ctx)));
Expand Down Expand Up @@ -170,6 +173,8 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
BuiltinFunction::ColorToStyledText => ALLOC_COST,
BuiltinFunction::OpenUrl => isize::MAX,
BuiltinFunction::MacosBringAllWindowsToFront => isize::MAX,
// Iterating the model and running the closure is unbounded; never inline.
BuiltinFunction::ArrayAny | BuiltinFunction::ArrayAll => isize::MAX,
}
}

Expand Down
Loading
Loading