| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned}; |
| 5 | use crate::langtype::{BuiltinElement, EnumerationValue, Function, Struct, Type}; |
| 6 | use crate::layout::Orientation; |
| 7 | use crate::lookup::LookupCtx; |
| 8 | use crate::object_tree::*; |
| 9 | use crate::parser::{NodeOrToken, SyntaxNode}; |
| 10 | use crate::typeregister; |
| 11 | use core::cell::RefCell; |
| 12 | use smol_str::{format_smolstr, SmolStr}; |
| 13 | use std::cell::Cell; |
| 14 | use std::collections::HashMap; |
| 15 | use std::rc::{Rc, Weak}; |
| 16 | |
| 17 | // FIXME remove the pub |
| 18 | pub use crate::namedreference::NamedReference; |
| 19 | pub use crate::passes::resolving; |
| 20 | |
| 21 | #[derive (Debug, Clone, PartialEq, Eq)] |
| 22 | /// A function built into the run-time |
| 23 | pub enum BuiltinFunction { |
| 24 | GetWindowScaleFactor, |
| 25 | GetWindowDefaultFontSize, |
| 26 | AnimationTick, |
| 27 | Debug, |
| 28 | Mod, |
| 29 | Round, |
| 30 | Ceil, |
| 31 | Floor, |
| 32 | Abs, |
| 33 | Sqrt, |
| 34 | Cos, |
| 35 | Sin, |
| 36 | Tan, |
| 37 | ACos, |
| 38 | ASin, |
| 39 | ATan, |
| 40 | ATan2, |
| 41 | Log, |
| 42 | Pow, |
| 43 | ToFixed, |
| 44 | ToPrecision, |
| 45 | SetFocusItem, |
| 46 | ClearFocusItem, |
| 47 | ShowPopupWindow, |
| 48 | ClosePopupWindow, |
| 49 | /// Show a context popup menu. |
| 50 | /// Arguments are `(parent, entries, position)` |
| 51 | /// |
| 52 | /// The first argument (parent) is a reference to the `ContectMenu` native item |
| 53 | /// The second argument (entries) can either be of type Array of MenuEntry, or a reference to a MenuItem tree. |
| 54 | /// When it is a menu item tree, it is a ElementReference to the root of the tree, and in the LLR, a NumberLiteral to an index in [`crate::llr::SubComponent::menu_item_trees`] |
| 55 | ShowPopupMenu, |
| 56 | SetSelectionOffsets, |
| 57 | ItemFontMetrics, |
| 58 | /// the "42".to_float() |
| 59 | StringToFloat, |
| 60 | /// the "42".is_float() |
| 61 | StringIsFloat, |
| 62 | /// the "42".is_empty |
| 63 | StringIsEmpty, |
| 64 | /// the "42".length |
| 65 | StringCharacterCount, |
| 66 | StringToLowercase, |
| 67 | StringToUppercase, |
| 68 | ColorRgbaStruct, |
| 69 | ColorHsvaStruct, |
| 70 | ColorBrighter, |
| 71 | ColorDarker, |
| 72 | ColorTransparentize, |
| 73 | ColorMix, |
| 74 | ColorWithAlpha, |
| 75 | ImageSize, |
| 76 | ArrayLength, |
| 77 | Rgb, |
| 78 | Hsv, |
| 79 | ColorScheme, |
| 80 | SupportsNativeMenuBar, |
| 81 | /// Setup the native menu bar, or the item-tree based menu bar |
| 82 | /// arguments are: `(ref entries, ref sub-menu, ref activated, item_tree_root?, no_native_menu_bar?)` |
| 83 | /// The two last arguments are only set if the menu is an item tree, in which case, `item_tree_root` is a reference |
| 84 | /// to the MenuItem tree root (just like the entries in the [`Self::ShowPopupMenu`] call), and `native_menu_bar` is |
| 85 | /// is a boolean literal that is true when we shouldn't try to setup the native menu bar. |
| 86 | /// If we have an item_tree_root, the code will assign the callback handler and properties on the non-native menubar as well |
| 87 | SetupNativeMenuBar, |
| 88 | Use24HourFormat, |
| 89 | MonthDayCount, |
| 90 | MonthOffset, |
| 91 | FormatDate, |
| 92 | DateNow, |
| 93 | ValidDate, |
| 94 | ParseDate, |
| 95 | TextInputFocused, |
| 96 | SetTextInputFocused, |
| 97 | ImplicitLayoutInfo(Orientation), |
| 98 | ItemAbsolutePosition, |
| 99 | RegisterCustomFontByPath, |
| 100 | RegisterCustomFontByMemory, |
| 101 | RegisterBitmapFont, |
| 102 | Translate, |
| 103 | UpdateTimers, |
| 104 | } |
| 105 | |
| 106 | #[derive (Debug, Clone)] |
| 107 | /// A builtin function which is handled by the compiler pass |
| 108 | /// |
| 109 | /// Builtin function expect their arguments in one and a specific type, so that's easier |
| 110 | /// for the generator. Macro however can do some transformation on their argument. |
| 111 | /// |
| 112 | pub enum BuiltinMacroFunction { |
| 113 | /// Transform `min(a, b, c, ..., z)` into a series of conditional expression and comparisons |
| 114 | Min, |
| 115 | /// Transform `max(a, b, c, ..., z)` into a series of conditional expression and comparisons |
| 116 | Max, |
| 117 | /// Transforms `clamp(v, min, max)` into a series of min/max calls |
| 118 | Clamp, |
| 119 | /// Add the right conversion operations so that the return type is the same as the argument type |
| 120 | Mod, |
| 121 | /// Add the right conversion operations so that the return type is the same as the argument type |
| 122 | Abs, |
| 123 | CubicBezier, |
| 124 | /// The argument can be r,g,b,a or r,g,b and they can be percentages or integer. |
| 125 | /// transform the argument so it is always rgb(r, g, b, a) with r, g, b between 0 and 255. |
| 126 | Rgb, |
| 127 | Hsv, |
| 128 | /// transform `debug(a, b, c)` into debug `a + " " + b + " " + c` |
| 129 | Debug, |
| 130 | } |
| 131 | |
| 132 | macro_rules! declare_builtin_function_types { |
| 133 | ($( $Name:ident $(($Pattern:tt))? : ($( $Arg:expr ),*) -> $ReturnType:expr $(,)? )*) => { |
| 134 | #[allow(non_snake_case)] |
| 135 | pub struct BuiltinFunctionTypes { |
| 136 | $(pub $Name : Rc<Function>),* |
| 137 | } |
| 138 | impl BuiltinFunctionTypes { |
| 139 | pub fn new() -> Self { |
| 140 | Self { |
| 141 | $($Name : Rc::new(Function{ |
| 142 | args: vec![$($Arg),*], |
| 143 | return_type: $ReturnType, |
| 144 | arg_names: vec![], |
| 145 | })),* |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | pub fn ty(&self, function: &BuiltinFunction) -> Rc<Function> { |
| 150 | match function { |
| 151 | $(BuiltinFunction::$Name $(($Pattern))? => self.$Name.clone()),* |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | }; |
| 156 | } |
| 157 | |
| 158 | declare_builtin_function_types!( |
| 159 | GetWindowScaleFactor: () -> Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)]), |
| 160 | GetWindowDefaultFontSize: () -> Type::LogicalLength, |
| 161 | AnimationTick: () -> Type::Duration, |
| 162 | Debug: (Type::String) -> Type::Void, |
| 163 | Mod: (Type::Int32, Type::Int32) -> Type::Int32, |
| 164 | Round: (Type::Float32) -> Type::Int32, |
| 165 | Ceil: (Type::Float32) -> Type::Int32, |
| 166 | Floor: (Type::Float32) -> Type::Int32, |
| 167 | Sqrt: (Type::Float32) -> Type::Float32, |
| 168 | Abs: (Type::Float32) -> Type::Float32, |
| 169 | Cos: (Type::Angle) -> Type::Float32, |
| 170 | Sin: (Type::Angle) -> Type::Float32, |
| 171 | Tan: (Type::Angle) -> Type::Float32, |
| 172 | ACos: (Type::Float32) -> Type::Angle, |
| 173 | ASin: (Type::Float32) -> Type::Angle, |
| 174 | ATan: (Type::Float32) -> Type::Angle, |
| 175 | ATan2: (Type::Float32, Type::Float32) -> Type::Angle, |
| 176 | Log: (Type::Float32, Type::Float32) -> Type::Float32, |
| 177 | Pow: (Type::Float32, Type::Float32) -> Type::Float32, |
| 178 | ToFixed: (Type::Float32, Type::Int32) -> Type::String, |
| 179 | ToPrecision: (Type::Float32, Type::Int32) -> Type::String, |
| 180 | SetFocusItem: (Type::ElementReference) -> Type::Void, |
| 181 | ClearFocusItem: (Type::ElementReference) -> Type::Void, |
| 182 | ShowPopupWindow: (Type::ElementReference) -> Type::Void, |
| 183 | ClosePopupWindow: (Type::ElementReference) -> Type::Void, |
| 184 | ShowPopupMenu: (Type::ElementReference, Type::Model, typeregister::logical_point_type()) -> Type::Void, |
| 185 | SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void, |
| 186 | ItemFontMetrics: (Type::ElementReference) -> typeregister::font_metrics_type(), |
| 187 | StringToFloat: (Type::String) -> Type::Float32, |
| 188 | StringIsFloat: (Type::String) -> Type::Bool, |
| 189 | StringIsEmpty: (Type::String) -> Type::Bool, |
| 190 | StringCharacterCount: (Type::String) -> Type::Int32, |
| 191 | StringToLowercase: (Type::String) -> Type::String, |
| 192 | StringToUppercase: (Type::String) -> Type::String, |
| 193 | ImplicitLayoutInfo(..): (Type::ElementReference) -> Type::Struct(typeregister::layout_info_type()), |
| 194 | ColorRgbaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct { |
| 195 | fields: IntoIterator::into_iter([ |
| 196 | (SmolStr::new_static("red" ), Type::Int32), |
| 197 | (SmolStr::new_static("green" ), Type::Int32), |
| 198 | (SmolStr::new_static("blue" ), Type::Int32), |
| 199 | (SmolStr::new_static("alpha" ), Type::Int32), |
| 200 | ]) |
| 201 | .collect(), |
| 202 | name: Some("Color" .into()), |
| 203 | node: None, |
| 204 | rust_attributes: None, |
| 205 | })), |
| 206 | ColorHsvaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct { |
| 207 | fields: IntoIterator::into_iter([ |
| 208 | (SmolStr::new_static("hue" ), Type::Float32), |
| 209 | (SmolStr::new_static("saturation" ), Type::Float32), |
| 210 | (SmolStr::new_static("value" ), Type::Float32), |
| 211 | (SmolStr::new_static("alpha" ), Type::Float32), |
| 212 | ]) |
| 213 | .collect(), |
| 214 | name: Some("Color" .into()), |
| 215 | node: None, |
| 216 | rust_attributes: None, |
| 217 | })), |
| 218 | ColorBrighter: (Type::Brush, Type::Float32) -> Type::Brush, |
| 219 | ColorDarker: (Type::Brush, Type::Float32) -> Type::Brush, |
| 220 | ColorTransparentize: (Type::Brush, Type::Float32) -> Type::Brush, |
| 221 | ColorWithAlpha: (Type::Brush, Type::Float32) -> Type::Brush, |
| 222 | ColorMix: (Type::Color, Type::Color, Type::Float32) -> Type::Color, |
| 223 | ImageSize: (Type::Image) -> Type::Struct(Rc::new(Struct { |
| 224 | fields: IntoIterator::into_iter([ |
| 225 | (SmolStr::new_static("width" ), Type::Int32), |
| 226 | (SmolStr::new_static("height" ), Type::Int32), |
| 227 | ]) |
| 228 | .collect(), |
| 229 | name: Some("Size" .into()), |
| 230 | node: None, |
| 231 | rust_attributes: None, |
| 232 | })), |
| 233 | ArrayLength: (Type::Model) -> Type::Int32, |
| 234 | Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color, |
| 235 | Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color, |
| 236 | ColorScheme: () -> Type::Enumeration( |
| 237 | typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone()), |
| 238 | ), |
| 239 | SupportsNativeMenuBar: () -> Type::Bool, |
| 240 | // entries, sub-menu, activate. But the types here are not accurate. |
| 241 | SetupNativeMenuBar: (Type::Model, typeregister::noarg_callback_type(), typeregister::noarg_callback_type()) -> Type::Void, |
| 242 | MonthDayCount: (Type::Int32, Type::Int32) -> Type::Int32, |
| 243 | MonthOffset: (Type::Int32, Type::Int32) -> Type::Int32, |
| 244 | FormatDate: (Type::String, Type::Int32, Type::Int32, Type::Int32) -> Type::String, |
| 245 | TextInputFocused: () -> Type::Bool, |
| 246 | DateNow: () -> Type::Array(Rc::new(Type::Int32)), |
| 247 | ValidDate: (Type::String, Type::String) -> Type::Bool, |
| 248 | ParseDate: (Type::String, Type::String) -> Type::Array(Rc::new(Type::Int32)), |
| 249 | SetTextInputFocused: (Type::Bool) -> Type::Void, |
| 250 | ItemAbsolutePosition: (Type::ElementReference) -> typeregister::logical_point_type(), |
| 251 | RegisterCustomFontByPath: (Type::String) -> Type::Void, |
| 252 | RegisterCustomFontByMemory: (Type::Int32) -> Type::Void, |
| 253 | RegisterBitmapFont: (Type::Int32) -> Type::Void, |
| 254 | // original, context, domain, args |
| 255 | Translate: (Type::String, Type::String, Type::String, Type::Array(Type::String.into())) -> Type::String, |
| 256 | Use24HourFormat: () -> Type::Bool, |
| 257 | UpdateTimers: () -> Type::Void, |
| 258 | ); |
| 259 | |
| 260 | impl BuiltinFunction { |
| 261 | pub fn ty(&self) -> Rc<Function> { |
| 262 | thread_local! { |
| 263 | static TYPES: BuiltinFunctionTypes = BuiltinFunctionTypes::new(); |
| 264 | } |
| 265 | TYPES.with(|types| types.ty(self)) |
| 266 | } |
| 267 | |
| 268 | /// It is const if the return value only depends on its argument and has no side effect |
| 269 | fn is_const(&self) -> bool { |
| 270 | match self { |
| 271 | BuiltinFunction::GetWindowScaleFactor => false, |
| 272 | BuiltinFunction::GetWindowDefaultFontSize => false, |
| 273 | BuiltinFunction::AnimationTick => false, |
| 274 | BuiltinFunction::ColorScheme => false, |
| 275 | BuiltinFunction::SupportsNativeMenuBar => false, |
| 276 | BuiltinFunction::SetupNativeMenuBar => false, |
| 277 | BuiltinFunction::MonthDayCount => false, |
| 278 | BuiltinFunction::MonthOffset => false, |
| 279 | BuiltinFunction::FormatDate => false, |
| 280 | BuiltinFunction::DateNow => false, |
| 281 | BuiltinFunction::ValidDate => false, |
| 282 | BuiltinFunction::ParseDate => false, |
| 283 | // Even if it is not pure, we optimize it away anyway |
| 284 | BuiltinFunction::Debug => true, |
| 285 | BuiltinFunction::Mod |
| 286 | | BuiltinFunction::Round |
| 287 | | BuiltinFunction::Ceil |
| 288 | | BuiltinFunction::Floor |
| 289 | | BuiltinFunction::Abs |
| 290 | | BuiltinFunction::Sqrt |
| 291 | | BuiltinFunction::Cos |
| 292 | | BuiltinFunction::Sin |
| 293 | | BuiltinFunction::Tan |
| 294 | | BuiltinFunction::ACos |
| 295 | | BuiltinFunction::ASin |
| 296 | | BuiltinFunction::Log |
| 297 | | BuiltinFunction::Pow |
| 298 | | BuiltinFunction::ATan |
| 299 | | BuiltinFunction::ATan2 |
| 300 | | BuiltinFunction::ToFixed |
| 301 | | BuiltinFunction::ToPrecision => true, |
| 302 | BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false, |
| 303 | BuiltinFunction::ShowPopupWindow |
| 304 | | BuiltinFunction::ClosePopupWindow |
| 305 | | BuiltinFunction::ShowPopupMenu => false, |
| 306 | BuiltinFunction::SetSelectionOffsets => false, |
| 307 | BuiltinFunction::ItemFontMetrics => false, // depends also on Window's font properties |
| 308 | BuiltinFunction::StringToFloat |
| 309 | | BuiltinFunction::StringIsFloat |
| 310 | | BuiltinFunction::StringIsEmpty |
| 311 | | BuiltinFunction::StringCharacterCount |
| 312 | | BuiltinFunction::StringToLowercase |
| 313 | | BuiltinFunction::StringToUppercase => true, |
| 314 | BuiltinFunction::ColorRgbaStruct |
| 315 | | BuiltinFunction::ColorHsvaStruct |
| 316 | | BuiltinFunction::ColorBrighter |
| 317 | | BuiltinFunction::ColorDarker |
| 318 | | BuiltinFunction::ColorTransparentize |
| 319 | | BuiltinFunction::ColorMix |
| 320 | | BuiltinFunction::ColorWithAlpha => true, |
| 321 | // ImageSize is pure, except when loading images via the network. Then the initial size will be 0/0 and |
| 322 | // we need to make sure that calls to this function stay within a binding, so that the property |
| 323 | // notification when updating kicks in. Only SlintPad (wasm-interpreter) loads images via the network, |
| 324 | // which is when this code is targeting wasm. |
| 325 | #[cfg (not(target_arch = "wasm32" ))] |
| 326 | BuiltinFunction::ImageSize => true, |
| 327 | #[cfg (target_arch = "wasm32" )] |
| 328 | BuiltinFunction::ImageSize => false, |
| 329 | BuiltinFunction::ArrayLength => true, |
| 330 | BuiltinFunction::Rgb => true, |
| 331 | BuiltinFunction::Hsv => true, |
| 332 | BuiltinFunction::SetTextInputFocused => false, |
| 333 | BuiltinFunction::TextInputFocused => false, |
| 334 | BuiltinFunction::ImplicitLayoutInfo(_) => false, |
| 335 | BuiltinFunction::ItemAbsolutePosition => true, |
| 336 | BuiltinFunction::RegisterCustomFontByPath |
| 337 | | BuiltinFunction::RegisterCustomFontByMemory |
| 338 | | BuiltinFunction::RegisterBitmapFont => false, |
| 339 | BuiltinFunction::Translate => false, |
| 340 | BuiltinFunction::Use24HourFormat => false, |
| 341 | BuiltinFunction::UpdateTimers => false, |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | // It is pure if it has no side effect |
| 346 | pub fn is_pure(&self) -> bool { |
| 347 | match self { |
| 348 | BuiltinFunction::GetWindowScaleFactor => true, |
| 349 | BuiltinFunction::GetWindowDefaultFontSize => true, |
| 350 | BuiltinFunction::AnimationTick => true, |
| 351 | BuiltinFunction::ColorScheme => true, |
| 352 | BuiltinFunction::SupportsNativeMenuBar => true, |
| 353 | BuiltinFunction::SetupNativeMenuBar => false, |
| 354 | BuiltinFunction::MonthDayCount => true, |
| 355 | BuiltinFunction::MonthOffset => true, |
| 356 | BuiltinFunction::FormatDate => true, |
| 357 | BuiltinFunction::DateNow => true, |
| 358 | BuiltinFunction::ValidDate => true, |
| 359 | BuiltinFunction::ParseDate => true, |
| 360 | // Even if it has technically side effect, we still consider it as pure for our purpose |
| 361 | BuiltinFunction::Debug => true, |
| 362 | BuiltinFunction::Mod |
| 363 | | BuiltinFunction::Round |
| 364 | | BuiltinFunction::Ceil |
| 365 | | BuiltinFunction::Floor |
| 366 | | BuiltinFunction::Abs |
| 367 | | BuiltinFunction::Sqrt |
| 368 | | BuiltinFunction::Cos |
| 369 | | BuiltinFunction::Sin |
| 370 | | BuiltinFunction::Tan |
| 371 | | BuiltinFunction::ACos |
| 372 | | BuiltinFunction::ASin |
| 373 | | BuiltinFunction::Log |
| 374 | | BuiltinFunction::Pow |
| 375 | | BuiltinFunction::ATan |
| 376 | | BuiltinFunction::ATan2 |
| 377 | | BuiltinFunction::ToFixed |
| 378 | | BuiltinFunction::ToPrecision => true, |
| 379 | BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false, |
| 380 | BuiltinFunction::ShowPopupWindow |
| 381 | | BuiltinFunction::ClosePopupWindow |
| 382 | | BuiltinFunction::ShowPopupMenu => false, |
| 383 | BuiltinFunction::SetSelectionOffsets => false, |
| 384 | BuiltinFunction::ItemFontMetrics => true, |
| 385 | BuiltinFunction::StringToFloat |
| 386 | | BuiltinFunction::StringIsFloat |
| 387 | | BuiltinFunction::StringIsEmpty |
| 388 | | BuiltinFunction::StringCharacterCount |
| 389 | | BuiltinFunction::StringToLowercase |
| 390 | | BuiltinFunction::StringToUppercase => true, |
| 391 | BuiltinFunction::ColorRgbaStruct |
| 392 | | BuiltinFunction::ColorHsvaStruct |
| 393 | | BuiltinFunction::ColorBrighter |
| 394 | | BuiltinFunction::ColorDarker |
| 395 | | BuiltinFunction::ColorTransparentize |
| 396 | | BuiltinFunction::ColorMix |
| 397 | | BuiltinFunction::ColorWithAlpha => true, |
| 398 | BuiltinFunction::ImageSize => true, |
| 399 | BuiltinFunction::ArrayLength => true, |
| 400 | BuiltinFunction::Rgb => true, |
| 401 | BuiltinFunction::Hsv => true, |
| 402 | BuiltinFunction::ImplicitLayoutInfo(_) => true, |
| 403 | BuiltinFunction::ItemAbsolutePosition => true, |
| 404 | BuiltinFunction::SetTextInputFocused => false, |
| 405 | BuiltinFunction::TextInputFocused => true, |
| 406 | BuiltinFunction::RegisterCustomFontByPath |
| 407 | | BuiltinFunction::RegisterCustomFontByMemory |
| 408 | | BuiltinFunction::RegisterBitmapFont => false, |
| 409 | BuiltinFunction::Translate => true, |
| 410 | BuiltinFunction::Use24HourFormat => true, |
| 411 | BuiltinFunction::UpdateTimers => false, |
| 412 | } |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | /// The base of a Expression::FunctionCall |
| 417 | #[derive (Debug, Clone)] |
| 418 | pub enum Callable { |
| 419 | Callback(NamedReference), |
| 420 | Function(NamedReference), |
| 421 | Builtin(BuiltinFunction), |
| 422 | } |
| 423 | impl Callable { |
| 424 | pub fn ty(&self) -> Type { |
| 425 | match self { |
| 426 | Callable::Callback(nr: &NamedReference) => nr.ty(), |
| 427 | Callable::Function(nr: &NamedReference) => nr.ty(), |
| 428 | Callable::Builtin(b: &BuiltinFunction) => Type::Function(b.ty()), |
| 429 | } |
| 430 | } |
| 431 | } |
| 432 | impl From<BuiltinFunction> for Callable { |
| 433 | fn from(function: BuiltinFunction) -> Self { |
| 434 | Self::Builtin(function) |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | #[derive (Debug, Clone, Eq, PartialEq)] |
| 439 | pub enum OperatorClass { |
| 440 | ComparisonOp, |
| 441 | LogicalOp, |
| 442 | ArithmeticOp, |
| 443 | } |
| 444 | |
| 445 | /// the class of for this (binary) operation |
| 446 | pub fn operator_class(op: char) -> OperatorClass { |
| 447 | match op { |
| 448 | '=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp, |
| 449 | '&' | '|' => OperatorClass::LogicalOp, |
| 450 | '+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp, |
| 451 | _ => panic!("Invalid operator {op:?}" ), |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | macro_rules! declare_units { |
| 456 | ($( $(#[$m:meta])* $ident:ident = $string:literal -> $ty:ident $(* $factor:expr)? ,)*) => { |
| 457 | /// The units that can be used after numbers in the language |
| 458 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)] |
| 459 | pub enum Unit { |
| 460 | $($(#[$m])* $ident,)* |
| 461 | } |
| 462 | |
| 463 | impl std::fmt::Display for Unit { |
| 464 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 465 | match self { |
| 466 | $(Self::$ident => write!(f, $string), )* |
| 467 | } |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | impl std::str::FromStr for Unit { |
| 472 | type Err = (); |
| 473 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 474 | match s { |
| 475 | $($string => Ok(Self::$ident), )* |
| 476 | _ => Err(()) |
| 477 | } |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | impl Unit { |
| 482 | pub fn ty(self) -> Type { |
| 483 | match self { |
| 484 | $(Self::$ident => Type::$ty, )* |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | pub fn normalize(self, x: f64) -> f64 { |
| 489 | match self { |
| 490 | $(Self::$ident => x $(* $factor as f64)?, )* |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | } |
| 495 | }; |
| 496 | } |
| 497 | |
| 498 | declare_units! { |
| 499 | /// No unit was given |
| 500 | None = "" -> Float32, |
| 501 | /// Percent value |
| 502 | Percent = "%" -> Percent, |
| 503 | |
| 504 | // Lengths or Coord |
| 505 | |
| 506 | /// Physical pixels |
| 507 | Phx = "phx" -> PhysicalLength, |
| 508 | /// Logical pixels |
| 509 | Px = "px" -> LogicalLength, |
| 510 | /// Centimeters |
| 511 | Cm = "cm" -> LogicalLength * 37.8, |
| 512 | /// Millimeters |
| 513 | Mm = "mm" -> LogicalLength * 3.78, |
| 514 | /// inches |
| 515 | In = "in" -> LogicalLength * 96, |
| 516 | /// Points |
| 517 | Pt = "pt" -> LogicalLength * 96./72., |
| 518 | /// Logical pixels multiplied with the window's default-font-size |
| 519 | Rem = "rem" -> Rem, |
| 520 | |
| 521 | // durations |
| 522 | |
| 523 | /// Seconds |
| 524 | S = "s" -> Duration * 1000, |
| 525 | /// Milliseconds |
| 526 | Ms = "ms" -> Duration, |
| 527 | |
| 528 | // angles |
| 529 | |
| 530 | /// Degree |
| 531 | Deg = "deg" -> Angle, |
| 532 | /// Gradians |
| 533 | Grad = "grad" -> Angle * 360./180., |
| 534 | /// Turns |
| 535 | Turn = "turn" -> Angle * 360., |
| 536 | /// Radians |
| 537 | Rad = "rad" -> Angle * 360./std::f32::consts::TAU, |
| 538 | } |
| 539 | |
| 540 | impl Default for Unit { |
| 541 | fn default() -> Self { |
| 542 | Self::None |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | #[derive (Debug, Clone, Copy)] |
| 547 | pub enum MinMaxOp { |
| 548 | Min, |
| 549 | Max, |
| 550 | } |
| 551 | |
| 552 | /// The Expression is hold by properties, so it should not hold any strong references to node from the object_tree |
| 553 | #[derive (Debug, Clone, Default)] |
| 554 | pub enum Expression { |
| 555 | /// Something went wrong (and an error will be reported) |
| 556 | #[default] |
| 557 | Invalid, |
| 558 | /// We haven't done the lookup yet |
| 559 | Uncompiled(SyntaxNode), |
| 560 | |
| 561 | /// A string literal. The .0 is the content of the string, without the quotes |
| 562 | StringLiteral(SmolStr), |
| 563 | /// Number |
| 564 | NumberLiteral(f64, Unit), |
| 565 | /// Bool |
| 566 | BoolLiteral(bool), |
| 567 | |
| 568 | /// Reference to the property |
| 569 | PropertyReference(NamedReference), |
| 570 | |
| 571 | /// A reference to a specific element. This isn't possible to create in .slint syntax itself, but intermediate passes may generate this |
| 572 | /// type of expression. |
| 573 | ElementReference(Weak<RefCell<Element>>), |
| 574 | |
| 575 | /// Reference to the index variable of a repeater |
| 576 | /// |
| 577 | /// Example: `idx` in `for xxx[idx] in ...`. The element is the reference to the |
| 578 | /// element that is repeated |
| 579 | RepeaterIndexReference { |
| 580 | element: Weak<RefCell<Element>>, |
| 581 | }, |
| 582 | |
| 583 | /// Reference to the model variable of a repeater |
| 584 | /// |
| 585 | /// Example: `xxx` in `for xxx[idx] in ...`. The element is the reference to the |
| 586 | /// element that is repeated |
| 587 | RepeaterModelReference { |
| 588 | element: Weak<RefCell<Element>>, |
| 589 | }, |
| 590 | |
| 591 | /// Reference the parameter at the given index of the current function. |
| 592 | FunctionParameterReference { |
| 593 | index: usize, |
| 594 | ty: Type, |
| 595 | }, |
| 596 | |
| 597 | /// Should be directly within a CodeBlock expression, and store the value of the expression in a local variable |
| 598 | StoreLocalVariable { |
| 599 | name: SmolStr, |
| 600 | value: Box<Expression>, |
| 601 | }, |
| 602 | |
| 603 | /// a reference to the local variable with the given name. The type system should ensure that a variable has been stored |
| 604 | /// with this name and this type before in one of the statement of an enclosing codeblock |
| 605 | ReadLocalVariable { |
| 606 | name: SmolStr, |
| 607 | ty: Type, |
| 608 | }, |
| 609 | |
| 610 | /// Access to a field of the given name within a struct. |
| 611 | StructFieldAccess { |
| 612 | /// This expression should have [`Type::Struct`] type |
| 613 | base: Box<Expression>, |
| 614 | name: SmolStr, |
| 615 | }, |
| 616 | |
| 617 | /// Access to a index within an array. |
| 618 | ArrayIndex { |
| 619 | /// This expression should have [`Type::Array`] type |
| 620 | array: Box<Expression>, |
| 621 | index: Box<Expression>, |
| 622 | }, |
| 623 | |
| 624 | /// Cast an expression to the given type |
| 625 | Cast { |
| 626 | from: Box<Expression>, |
| 627 | to: Type, |
| 628 | }, |
| 629 | |
| 630 | /// a code block with different expression |
| 631 | CodeBlock(Vec<Expression>), |
| 632 | |
| 633 | /// A function call |
| 634 | FunctionCall { |
| 635 | function: Callable, |
| 636 | arguments: Vec<Expression>, |
| 637 | source_location: Option<SourceLocation>, |
| 638 | }, |
| 639 | |
| 640 | /// A SelfAssignment or an Assignment. When op is '=' this is a simple assignment. |
| 641 | SelfAssignment { |
| 642 | lhs: Box<Expression>, |
| 643 | rhs: Box<Expression>, |
| 644 | /// '+', '-', '/', '*', or '=' |
| 645 | op: char, |
| 646 | node: Option<NodeOrToken>, |
| 647 | }, |
| 648 | |
| 649 | BinaryExpression { |
| 650 | lhs: Box<Expression>, |
| 651 | rhs: Box<Expression>, |
| 652 | /// '+', '-', '/', '*', '=', '!', '<', '>', '≤', '≥', '&', '|' |
| 653 | op: char, |
| 654 | }, |
| 655 | |
| 656 | UnaryOp { |
| 657 | sub: Box<Expression>, |
| 658 | /// '+', '-', '!' |
| 659 | op: char, |
| 660 | }, |
| 661 | |
| 662 | ImageReference { |
| 663 | resource_ref: ImageReference, |
| 664 | source_location: Option<SourceLocation>, |
| 665 | nine_slice: Option<[u16; 4]>, |
| 666 | }, |
| 667 | |
| 668 | Condition { |
| 669 | condition: Box<Expression>, |
| 670 | true_expr: Box<Expression>, |
| 671 | false_expr: Box<Expression>, |
| 672 | }, |
| 673 | |
| 674 | Array { |
| 675 | element_ty: Type, |
| 676 | values: Vec<Expression>, |
| 677 | }, |
| 678 | Struct { |
| 679 | ty: Rc<Struct>, |
| 680 | values: HashMap<SmolStr, Expression>, |
| 681 | }, |
| 682 | |
| 683 | PathData(Path), |
| 684 | |
| 685 | EasingCurve(EasingCurve), |
| 686 | |
| 687 | LinearGradient { |
| 688 | angle: Box<Expression>, |
| 689 | /// First expression in the tuple is a color, second expression is the stop position |
| 690 | stops: Vec<(Expression, Expression)>, |
| 691 | }, |
| 692 | |
| 693 | RadialGradient { |
| 694 | /// First expression in the tuple is a color, second expression is the stop position |
| 695 | stops: Vec<(Expression, Expression)>, |
| 696 | }, |
| 697 | |
| 698 | EnumerationValue(EnumerationValue), |
| 699 | |
| 700 | ReturnStatement(Option<Box<Expression>>), |
| 701 | |
| 702 | LayoutCacheAccess { |
| 703 | layout_cache_prop: NamedReference, |
| 704 | index: usize, |
| 705 | /// When set, this is the index within a repeater, and the index is then the location of another offset. |
| 706 | /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index]` |
| 707 | repeater_index: Option<Box<Expression>>, |
| 708 | }, |
| 709 | /// Compute the LayoutInfo for the given layout. |
| 710 | /// The orientation is the orientation of the cache, not the orientation of the layout |
| 711 | ComputeLayoutInfo(crate::layout::Layout, crate::layout::Orientation), |
| 712 | SolveLayout(crate::layout::Layout, crate::layout::Orientation), |
| 713 | |
| 714 | MinMax { |
| 715 | ty: Type, |
| 716 | op: MinMaxOp, |
| 717 | lhs: Box<Expression>, |
| 718 | rhs: Box<Expression>, |
| 719 | }, |
| 720 | |
| 721 | DebugHook { |
| 722 | expression: Box<Expression>, |
| 723 | id: SmolStr, |
| 724 | }, |
| 725 | |
| 726 | EmptyComponentFactory, |
| 727 | } |
| 728 | |
| 729 | impl Expression { |
| 730 | /// Return the type of this property |
| 731 | pub fn ty(&self) -> Type { |
| 732 | match self { |
| 733 | Expression::Invalid => Type::Invalid, |
| 734 | Expression::Uncompiled(_) => Type::Invalid, |
| 735 | Expression::StringLiteral(_) => Type::String, |
| 736 | Expression::NumberLiteral(_, unit) => unit.ty(), |
| 737 | Expression::BoolLiteral(_) => Type::Bool, |
| 738 | Expression::PropertyReference(nr) => nr.ty(), |
| 739 | Expression::ElementReference(_) => Type::ElementReference, |
| 740 | Expression::RepeaterIndexReference { .. } => Type::Int32, |
| 741 | Expression::RepeaterModelReference { element } => element |
| 742 | .upgrade() |
| 743 | .unwrap() |
| 744 | .borrow() |
| 745 | .repeated |
| 746 | .as_ref() |
| 747 | .map_or(Type::Invalid, |e| model_inner_type(&e.model)), |
| 748 | Expression::FunctionParameterReference { ty, .. } => ty.clone(), |
| 749 | Expression::StructFieldAccess { base, name } => match base.ty() { |
| 750 | Type::Struct(s) => s.fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone(), |
| 751 | _ => Type::Invalid, |
| 752 | }, |
| 753 | Expression::ArrayIndex { array, .. } => match array.ty() { |
| 754 | Type::Array(ty) => (*ty).clone(), |
| 755 | _ => Type::Invalid, |
| 756 | }, |
| 757 | Expression::Cast { to, .. } => to.clone(), |
| 758 | Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()), |
| 759 | Expression::FunctionCall { function, .. } => match function.ty() { |
| 760 | Type::Function(f) | Type::Callback(f) => f.return_type.clone(), |
| 761 | _ => Type::Invalid, |
| 762 | }, |
| 763 | Expression::SelfAssignment { .. } => Type::Void, |
| 764 | Expression::ImageReference { .. } => Type::Image, |
| 765 | Expression::Condition { condition: _, true_expr, false_expr } => { |
| 766 | let true_type = true_expr.ty(); |
| 767 | let false_type = false_expr.ty(); |
| 768 | if true_type == false_type { |
| 769 | true_type |
| 770 | } else if true_type == Type::Invalid { |
| 771 | false_type |
| 772 | } else if false_type == Type::Invalid { |
| 773 | true_type |
| 774 | } else { |
| 775 | Type::Void |
| 776 | } |
| 777 | } |
| 778 | Expression::BinaryExpression { op, lhs, rhs } => { |
| 779 | if operator_class(*op) != OperatorClass::ArithmeticOp { |
| 780 | Type::Bool |
| 781 | } else if *op == '+' || *op == '-' { |
| 782 | let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty()); |
| 783 | if rhs_ty == lhs_ty { |
| 784 | rhs_ty |
| 785 | } else { |
| 786 | Type::Invalid |
| 787 | } |
| 788 | } else { |
| 789 | debug_assert!(*op == '*' || *op == '/' ); |
| 790 | let unit_vec = |ty| { |
| 791 | if let Type::UnitProduct(v) = ty { |
| 792 | v |
| 793 | } else if let Some(u) = ty.default_unit() { |
| 794 | vec![(u, 1)] |
| 795 | } else { |
| 796 | vec![] |
| 797 | } |
| 798 | }; |
| 799 | let mut l_units = unit_vec(lhs.ty()); |
| 800 | let mut r_units = unit_vec(rhs.ty()); |
| 801 | if *op == '/' { |
| 802 | for (_, power) in &mut r_units { |
| 803 | *power = -*power; |
| 804 | } |
| 805 | } |
| 806 | for (unit, power) in r_units { |
| 807 | if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) { |
| 808 | *p += power; |
| 809 | } else { |
| 810 | l_units.push((unit, power)); |
| 811 | } |
| 812 | } |
| 813 | |
| 814 | // normalize the vector by removing empty and sorting |
| 815 | l_units.retain(|(_, p)| *p != 0); |
| 816 | l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) { |
| 817 | std::cmp::Ordering::Equal => u1.cmp(u2), |
| 818 | x => x, |
| 819 | }); |
| 820 | |
| 821 | if l_units.is_empty() { |
| 822 | Type::Float32 |
| 823 | } else if l_units.len() == 1 && l_units[0].1 == 1 { |
| 824 | l_units[0].0.ty() |
| 825 | } else { |
| 826 | Type::UnitProduct(l_units) |
| 827 | } |
| 828 | } |
| 829 | } |
| 830 | Expression::UnaryOp { sub, .. } => sub.ty(), |
| 831 | Expression::Array { element_ty, .. } => Type::Array(Rc::new(element_ty.clone())), |
| 832 | Expression::Struct { ty, .. } => ty.clone().into(), |
| 833 | Expression::PathData { .. } => Type::PathData, |
| 834 | Expression::StoreLocalVariable { .. } => Type::Void, |
| 835 | Expression::ReadLocalVariable { ty, .. } => ty.clone(), |
| 836 | Expression::EasingCurve(_) => Type::Easing, |
| 837 | Expression::LinearGradient { .. } => Type::Brush, |
| 838 | Expression::RadialGradient { .. } => Type::Brush, |
| 839 | Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()), |
| 840 | // invalid because the expression is unreachable |
| 841 | Expression::ReturnStatement(_) => Type::Invalid, |
| 842 | Expression::LayoutCacheAccess { .. } => Type::LogicalLength, |
| 843 | Expression::ComputeLayoutInfo(..) => typeregister::layout_info_type().into(), |
| 844 | Expression::SolveLayout(..) => Type::LayoutCache, |
| 845 | Expression::MinMax { ty, .. } => ty.clone(), |
| 846 | Expression::EmptyComponentFactory => Type::ComponentFactory, |
| 847 | Expression::DebugHook { expression, .. } => expression.ty(), |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | /// Call the visitor for each sub-expression. (note: this function does not recurse) |
| 852 | pub fn visit(&self, mut visitor: impl FnMut(&Self)) { |
| 853 | match self { |
| 854 | Expression::Invalid => {} |
| 855 | Expression::Uncompiled(_) => {} |
| 856 | Expression::StringLiteral(_) => {} |
| 857 | Expression::NumberLiteral(_, _) => {} |
| 858 | Expression::BoolLiteral(_) => {} |
| 859 | Expression::PropertyReference { .. } => {} |
| 860 | Expression::FunctionParameterReference { .. } => {} |
| 861 | Expression::ElementReference(_) => {} |
| 862 | Expression::StructFieldAccess { base, .. } => visitor(base), |
| 863 | Expression::ArrayIndex { array, index } => { |
| 864 | visitor(array); |
| 865 | visitor(index); |
| 866 | } |
| 867 | Expression::RepeaterIndexReference { .. } => {} |
| 868 | Expression::RepeaterModelReference { .. } => {} |
| 869 | Expression::Cast { from, .. } => visitor(from), |
| 870 | Expression::CodeBlock(sub) => { |
| 871 | sub.iter().for_each(visitor); |
| 872 | } |
| 873 | Expression::FunctionCall { function: _, arguments, source_location: _ } => { |
| 874 | arguments.iter().for_each(visitor); |
| 875 | } |
| 876 | Expression::SelfAssignment { lhs, rhs, .. } => { |
| 877 | visitor(lhs); |
| 878 | visitor(rhs); |
| 879 | } |
| 880 | Expression::ImageReference { .. } => {} |
| 881 | Expression::Condition { condition, true_expr, false_expr } => { |
| 882 | visitor(condition); |
| 883 | visitor(true_expr); |
| 884 | visitor(false_expr); |
| 885 | } |
| 886 | Expression::BinaryExpression { lhs, rhs, .. } => { |
| 887 | visitor(lhs); |
| 888 | visitor(rhs); |
| 889 | } |
| 890 | Expression::UnaryOp { sub, .. } => visitor(sub), |
| 891 | Expression::Array { values, .. } => { |
| 892 | for x in values { |
| 893 | visitor(x); |
| 894 | } |
| 895 | } |
| 896 | Expression::Struct { values, .. } => { |
| 897 | for x in values.values() { |
| 898 | visitor(x); |
| 899 | } |
| 900 | } |
| 901 | Expression::PathData(data) => match data { |
| 902 | Path::Elements(elements) => { |
| 903 | for element in elements { |
| 904 | element.bindings.values().for_each(|binding| visitor(&binding.borrow())) |
| 905 | } |
| 906 | } |
| 907 | Path::Events(events, coordinates) => { |
| 908 | events.iter().chain(coordinates.iter()).for_each(visitor); |
| 909 | } |
| 910 | Path::Commands(commands) => visitor(commands), |
| 911 | }, |
| 912 | Expression::StoreLocalVariable { value, .. } => visitor(value), |
| 913 | Expression::ReadLocalVariable { .. } => {} |
| 914 | Expression::EasingCurve(_) => {} |
| 915 | Expression::LinearGradient { angle, stops } => { |
| 916 | visitor(angle); |
| 917 | for (c, s) in stops { |
| 918 | visitor(c); |
| 919 | visitor(s); |
| 920 | } |
| 921 | } |
| 922 | Expression::RadialGradient { stops } => { |
| 923 | for (c, s) in stops { |
| 924 | visitor(c); |
| 925 | visitor(s); |
| 926 | } |
| 927 | } |
| 928 | Expression::EnumerationValue(_) => {} |
| 929 | Expression::ReturnStatement(expr) => { |
| 930 | expr.as_deref().map(visitor); |
| 931 | } |
| 932 | Expression::LayoutCacheAccess { repeater_index, .. } => { |
| 933 | repeater_index.as_deref().map(visitor); |
| 934 | } |
| 935 | Expression::ComputeLayoutInfo(..) => {} |
| 936 | Expression::SolveLayout(..) => {} |
| 937 | Expression::MinMax { lhs, rhs, .. } => { |
| 938 | visitor(lhs); |
| 939 | visitor(rhs); |
| 940 | } |
| 941 | Expression::EmptyComponentFactory => {} |
| 942 | Expression::DebugHook { expression, .. } => visitor(expression), |
| 943 | } |
| 944 | } |
| 945 | |
| 946 | pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) { |
| 947 | match self { |
| 948 | Expression::Invalid => {} |
| 949 | Expression::Uncompiled(_) => {} |
| 950 | Expression::StringLiteral(_) => {} |
| 951 | Expression::NumberLiteral(_, _) => {} |
| 952 | Expression::BoolLiteral(_) => {} |
| 953 | Expression::PropertyReference { .. } => {} |
| 954 | Expression::FunctionParameterReference { .. } => {} |
| 955 | Expression::ElementReference(_) => {} |
| 956 | Expression::StructFieldAccess { base, .. } => visitor(base), |
| 957 | Expression::ArrayIndex { array, index } => { |
| 958 | visitor(array); |
| 959 | visitor(index); |
| 960 | } |
| 961 | Expression::RepeaterIndexReference { .. } => {} |
| 962 | Expression::RepeaterModelReference { .. } => {} |
| 963 | Expression::Cast { from, .. } => visitor(from), |
| 964 | Expression::CodeBlock(sub) => { |
| 965 | sub.iter_mut().for_each(visitor); |
| 966 | } |
| 967 | Expression::FunctionCall { function: _, arguments, source_location: _ } => { |
| 968 | arguments.iter_mut().for_each(visitor); |
| 969 | } |
| 970 | Expression::SelfAssignment { lhs, rhs, .. } => { |
| 971 | visitor(lhs); |
| 972 | visitor(rhs); |
| 973 | } |
| 974 | Expression::ImageReference { .. } => {} |
| 975 | Expression::Condition { condition, true_expr, false_expr } => { |
| 976 | visitor(condition); |
| 977 | visitor(true_expr); |
| 978 | visitor(false_expr); |
| 979 | } |
| 980 | Expression::BinaryExpression { lhs, rhs, .. } => { |
| 981 | visitor(lhs); |
| 982 | visitor(rhs); |
| 983 | } |
| 984 | Expression::UnaryOp { sub, .. } => visitor(sub), |
| 985 | Expression::Array { values, .. } => { |
| 986 | for x in values { |
| 987 | visitor(x); |
| 988 | } |
| 989 | } |
| 990 | Expression::Struct { values, .. } => { |
| 991 | for x in values.values_mut() { |
| 992 | visitor(x); |
| 993 | } |
| 994 | } |
| 995 | Expression::PathData(data) => match data { |
| 996 | Path::Elements(elements) => { |
| 997 | for element in elements { |
| 998 | element |
| 999 | .bindings |
| 1000 | .values_mut() |
| 1001 | .for_each(|binding| visitor(&mut binding.borrow_mut())) |
| 1002 | } |
| 1003 | } |
| 1004 | Path::Events(events, coordinates) => { |
| 1005 | events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor); |
| 1006 | } |
| 1007 | Path::Commands(commands) => visitor(commands), |
| 1008 | }, |
| 1009 | Expression::StoreLocalVariable { value, .. } => visitor(value), |
| 1010 | Expression::ReadLocalVariable { .. } => {} |
| 1011 | Expression::EasingCurve(_) => {} |
| 1012 | Expression::LinearGradient { angle, stops } => { |
| 1013 | visitor(angle); |
| 1014 | for (c, s) in stops { |
| 1015 | visitor(c); |
| 1016 | visitor(s); |
| 1017 | } |
| 1018 | } |
| 1019 | Expression::RadialGradient { stops } => { |
| 1020 | for (c, s) in stops { |
| 1021 | visitor(c); |
| 1022 | visitor(s); |
| 1023 | } |
| 1024 | } |
| 1025 | Expression::EnumerationValue(_) => {} |
| 1026 | Expression::ReturnStatement(expr) => { |
| 1027 | expr.as_deref_mut().map(visitor); |
| 1028 | } |
| 1029 | Expression::LayoutCacheAccess { repeater_index, .. } => { |
| 1030 | repeater_index.as_deref_mut().map(visitor); |
| 1031 | } |
| 1032 | Expression::ComputeLayoutInfo(..) => {} |
| 1033 | Expression::SolveLayout(..) => {} |
| 1034 | Expression::MinMax { lhs, rhs, .. } => { |
| 1035 | visitor(lhs); |
| 1036 | visitor(rhs); |
| 1037 | } |
| 1038 | Expression::EmptyComponentFactory => {} |
| 1039 | Expression::DebugHook { expression, .. } => visitor(expression), |
| 1040 | } |
| 1041 | } |
| 1042 | |
| 1043 | /// Visit itself and each sub expression recursively |
| 1044 | pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) { |
| 1045 | visitor(self); |
| 1046 | self.visit(|e| e.visit_recursive(visitor)); |
| 1047 | } |
| 1048 | |
| 1049 | /// Visit itself and each sub expression recursively |
| 1050 | pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) { |
| 1051 | visitor(self); |
| 1052 | self.visit_mut(|e| e.visit_recursive_mut(visitor)); |
| 1053 | } |
| 1054 | |
| 1055 | pub fn is_constant(&self) -> bool { |
| 1056 | match self { |
| 1057 | Expression::Invalid => true, |
| 1058 | Expression::Uncompiled(_) => false, |
| 1059 | Expression::StringLiteral(_) => true, |
| 1060 | Expression::NumberLiteral(_, _) => true, |
| 1061 | Expression::BoolLiteral(_) => true, |
| 1062 | Expression::PropertyReference(nr) => nr.is_constant(), |
| 1063 | Expression::ElementReference(_) => false, |
| 1064 | Expression::RepeaterIndexReference { .. } => false, |
| 1065 | Expression::RepeaterModelReference { .. } => false, |
| 1066 | // Allow functions to be marked as const |
| 1067 | Expression::FunctionParameterReference { .. } => true, |
| 1068 | Expression::StructFieldAccess { base, .. } => base.is_constant(), |
| 1069 | Expression::ArrayIndex { array, index } => array.is_constant() && index.is_constant(), |
| 1070 | Expression::Cast { from, .. } => from.is_constant(), |
| 1071 | // This is conservative: the return value is the last expression in the block, but |
| 1072 | // we kind of mean "pure" here too, so ensure the whole body is OK. |
| 1073 | Expression::CodeBlock(sub) => sub.iter().all(|s| s.is_constant()), |
| 1074 | Expression::FunctionCall { function, arguments, .. } => { |
| 1075 | let is_const = match function { |
| 1076 | Callable::Builtin(b) => b.is_const(), |
| 1077 | Callable::Function(nr) => nr.is_constant(), |
| 1078 | Callable::Callback(..) => false, |
| 1079 | }; |
| 1080 | is_const && arguments.iter().all(|a| a.is_constant()) |
| 1081 | } |
| 1082 | Expression::SelfAssignment { .. } => false, |
| 1083 | Expression::ImageReference { .. } => true, |
| 1084 | Expression::Condition { condition, false_expr, true_expr } => { |
| 1085 | condition.is_constant() && false_expr.is_constant() && true_expr.is_constant() |
| 1086 | } |
| 1087 | Expression::BinaryExpression { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(), |
| 1088 | Expression::UnaryOp { sub, .. } => sub.is_constant(), |
| 1089 | // Array will turn into model, and they can't be considered as constant if the model |
| 1090 | // is used and the model is changed. CF issue #5249 |
| 1091 | //Expression::Array { values, .. } => values.iter().all(Expression::is_constant), |
| 1092 | Expression::Array { .. } => false, |
| 1093 | Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant()), |
| 1094 | Expression::PathData(data) => match data { |
| 1095 | Path::Elements(elements) => elements |
| 1096 | .iter() |
| 1097 | .all(|element| element.bindings.values().all(|v| v.borrow().is_constant())), |
| 1098 | Path::Events(_, _) => true, |
| 1099 | Path::Commands(_) => false, |
| 1100 | }, |
| 1101 | Expression::StoreLocalVariable { value, .. } => value.is_constant(), |
| 1102 | // We only load what we store, and stores are alredy checked |
| 1103 | Expression::ReadLocalVariable { .. } => true, |
| 1104 | Expression::EasingCurve(_) => true, |
| 1105 | Expression::LinearGradient { angle, stops } => { |
| 1106 | angle.is_constant() && stops.iter().all(|(c, s)| c.is_constant() && s.is_constant()) |
| 1107 | } |
| 1108 | Expression::RadialGradient { stops } => { |
| 1109 | stops.iter().all(|(c, s)| c.is_constant() && s.is_constant()) |
| 1110 | } |
| 1111 | Expression::EnumerationValue(_) => true, |
| 1112 | Expression::ReturnStatement(expr) => { |
| 1113 | expr.as_ref().map_or(true, |expr| expr.is_constant()) |
| 1114 | } |
| 1115 | // TODO: detect constant property within layouts |
| 1116 | Expression::LayoutCacheAccess { .. } => false, |
| 1117 | Expression::ComputeLayoutInfo(..) => false, |
| 1118 | Expression::SolveLayout(..) => false, |
| 1119 | Expression::MinMax { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(), |
| 1120 | Expression::EmptyComponentFactory => true, |
| 1121 | Expression::DebugHook { .. } => false, |
| 1122 | } |
| 1123 | } |
| 1124 | |
| 1125 | /// Create a conversion node if needed, or throw an error if the type is not matching |
| 1126 | #[must_use ] |
| 1127 | pub fn maybe_convert_to( |
| 1128 | self, |
| 1129 | target_type: Type, |
| 1130 | node: &dyn Spanned, |
| 1131 | diag: &mut BuildDiagnostics, |
| 1132 | ) -> Expression { |
| 1133 | let ty = self.ty(); |
| 1134 | if ty == target_type |
| 1135 | || target_type == Type::Void |
| 1136 | || target_type == Type::Invalid |
| 1137 | || ty == Type::Invalid |
| 1138 | { |
| 1139 | self |
| 1140 | } else if ty.can_convert(&target_type) { |
| 1141 | let from = match (ty, &target_type) { |
| 1142 | (Type::Brush, Type::Color) => match self { |
| 1143 | Expression::LinearGradient { .. } | Expression::RadialGradient { .. } => { |
| 1144 | let message = format!("Narrowing conversion from {0} to {1}. This can lead to unexpected behavior because the {0} is a gradient" , Type::Brush, Type::Color); |
| 1145 | diag.push_warning(message, node); |
| 1146 | self |
| 1147 | } |
| 1148 | _ => self, |
| 1149 | }, |
| 1150 | (Type::Percent, Type::Float32) => Expression::BinaryExpression { |
| 1151 | lhs: Box::new(self), |
| 1152 | rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)), |
| 1153 | op: '*' , |
| 1154 | }, |
| 1155 | (ref from_ty @ Type::Struct(ref left), Type::Struct(right)) |
| 1156 | if left.fields != right.fields => |
| 1157 | { |
| 1158 | if let Expression::Struct { mut values, .. } = self { |
| 1159 | let mut new_values = HashMap::new(); |
| 1160 | for (key, ty) in &right.fields { |
| 1161 | let (key, expression) = values.remove_entry(key).map_or_else( |
| 1162 | || (key.clone(), Expression::default_value_for_type(ty)), |
| 1163 | |(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)), |
| 1164 | ); |
| 1165 | new_values.insert(key, expression); |
| 1166 | } |
| 1167 | return Expression::Struct { values: new_values, ty: right.clone() }; |
| 1168 | } |
| 1169 | static COUNT: std::sync::atomic::AtomicUsize = |
| 1170 | std::sync::atomic::AtomicUsize::new(0); |
| 1171 | let var_name = format_smolstr!( |
| 1172 | "tmpobj_conv_ {}" , |
| 1173 | COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) |
| 1174 | ); |
| 1175 | let mut new_values = HashMap::new(); |
| 1176 | for (key, ty) in &right.fields { |
| 1177 | let expression = if left.fields.contains_key(key) { |
| 1178 | Expression::StructFieldAccess { |
| 1179 | base: Box::new(Expression::ReadLocalVariable { |
| 1180 | name: var_name.clone(), |
| 1181 | ty: from_ty.clone(), |
| 1182 | }), |
| 1183 | name: key.clone(), |
| 1184 | } |
| 1185 | .maybe_convert_to(ty.clone(), node, diag) |
| 1186 | } else { |
| 1187 | Expression::default_value_for_type(ty) |
| 1188 | }; |
| 1189 | new_values.insert(key.clone(), expression); |
| 1190 | } |
| 1191 | return Expression::CodeBlock(vec![ |
| 1192 | Expression::StoreLocalVariable { name: var_name, value: Box::new(self) }, |
| 1193 | Expression::Struct { values: new_values, ty: right.clone() }, |
| 1194 | ]); |
| 1195 | } |
| 1196 | (left, right) => match (left.as_unit_product(), right.as_unit_product()) { |
| 1197 | (Some(left), Some(right)) => { |
| 1198 | if let Some(conversion_powers) = |
| 1199 | crate::langtype::unit_product_length_conversion(&left, &right) |
| 1200 | { |
| 1201 | let apply_power = |
| 1202 | |mut result, power: i8, builtin_fn: BuiltinFunction| { |
| 1203 | let op = if power < 0 { '*' } else { '/' }; |
| 1204 | for _ in 0..power.abs() { |
| 1205 | result = Expression::BinaryExpression { |
| 1206 | lhs: Box::new(result), |
| 1207 | rhs: Box::new(Expression::FunctionCall { |
| 1208 | function: Callable::Builtin(builtin_fn.clone()), |
| 1209 | arguments: vec![], |
| 1210 | source_location: Some(node.to_source_location()), |
| 1211 | }), |
| 1212 | op, |
| 1213 | } |
| 1214 | } |
| 1215 | result |
| 1216 | }; |
| 1217 | |
| 1218 | let mut result = self; |
| 1219 | |
| 1220 | if conversion_powers.rem_to_px_power != 0 { |
| 1221 | result = apply_power( |
| 1222 | result, |
| 1223 | conversion_powers.rem_to_px_power, |
| 1224 | BuiltinFunction::GetWindowDefaultFontSize, |
| 1225 | ) |
| 1226 | } |
| 1227 | if conversion_powers.px_to_phx_power != 0 { |
| 1228 | result = apply_power( |
| 1229 | result, |
| 1230 | conversion_powers.px_to_phx_power, |
| 1231 | BuiltinFunction::GetWindowScaleFactor, |
| 1232 | ) |
| 1233 | } |
| 1234 | |
| 1235 | result |
| 1236 | } else { |
| 1237 | self |
| 1238 | } |
| 1239 | } |
| 1240 | _ => self, |
| 1241 | }, |
| 1242 | }; |
| 1243 | Expression::Cast { from: Box::new(from), to: target_type } |
| 1244 | } else if matches!( |
| 1245 | (&ty, &target_type, &self), |
| 1246 | (Type::Array(_), Type::Array(_), Expression::Array { .. }) |
| 1247 | ) { |
| 1248 | // Special case for converting array literals |
| 1249 | match (self, target_type) { |
| 1250 | (Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array { |
| 1251 | values: values |
| 1252 | .into_iter() |
| 1253 | .map(|e| e.maybe_convert_to((*target_type).clone(), node, diag)) |
| 1254 | .take_while(|e| !matches!(e, Expression::Invalid)) |
| 1255 | .collect(), |
| 1256 | element_ty: (*target_type).clone(), |
| 1257 | }, |
| 1258 | _ => unreachable!(), |
| 1259 | } |
| 1260 | } else if let (Type::Struct(struct_type), Expression::Struct { values, .. }) = |
| 1261 | (&target_type, &self) |
| 1262 | { |
| 1263 | // Also special case struct literal in case they contain array literal |
| 1264 | let mut fields = struct_type.fields.clone(); |
| 1265 | let mut new_values = HashMap::new(); |
| 1266 | for (f, v) in values { |
| 1267 | if let Some(t) = fields.remove(f) { |
| 1268 | new_values.insert(f.clone(), v.clone().maybe_convert_to(t, node, diag)); |
| 1269 | } else { |
| 1270 | diag.push_error(format!("Cannot convert {ty} to {target_type}" ), node); |
| 1271 | return self; |
| 1272 | } |
| 1273 | } |
| 1274 | for (f, t) in fields { |
| 1275 | new_values.insert(f, Expression::default_value_for_type(&t)); |
| 1276 | } |
| 1277 | Expression::Struct { ty: struct_type.clone(), values: new_values } |
| 1278 | } else { |
| 1279 | let mut message = format!("Cannot convert {ty} to {target_type}" ); |
| 1280 | // Explicit error message for unit conversion |
| 1281 | if let Some(from_unit) = ty.default_unit() { |
| 1282 | if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) { |
| 1283 | message = |
| 1284 | format!(" {message}. Divide by 1 {from_unit} to convert to a plain number" ); |
| 1285 | } |
| 1286 | } else if let Some(to_unit) = target_type.default_unit() { |
| 1287 | if matches!(ty, Type::Int32 | Type::Float32) { |
| 1288 | if let Expression::NumberLiteral(value, Unit::None) = self { |
| 1289 | if value == 0. { |
| 1290 | // Allow conversion from literal 0 to any unit |
| 1291 | return Expression::NumberLiteral(0., to_unit); |
| 1292 | } |
| 1293 | } |
| 1294 | message = format!( |
| 1295 | " {message}. Use an unit, or multiply by 1 {to_unit} to convert explicitly" |
| 1296 | ); |
| 1297 | } |
| 1298 | } |
| 1299 | diag.push_error(message, node); |
| 1300 | self |
| 1301 | } |
| 1302 | } |
| 1303 | |
| 1304 | /// Return the default value for the given type |
| 1305 | pub fn default_value_for_type(ty: &Type) -> Expression { |
| 1306 | match ty { |
| 1307 | Type::Invalid |
| 1308 | | Type::Callback { .. } |
| 1309 | | Type::Function { .. } |
| 1310 | | Type::InferredProperty |
| 1311 | | Type::InferredCallback |
| 1312 | | Type::ElementReference |
| 1313 | | Type::LayoutCache => Expression::Invalid, |
| 1314 | Type::Void => Expression::CodeBlock(vec![]), |
| 1315 | Type::Float32 => Expression::NumberLiteral(0., Unit::None), |
| 1316 | Type::String => Expression::StringLiteral(SmolStr::default()), |
| 1317 | Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast { |
| 1318 | from: Box::new(Expression::NumberLiteral(0., Unit::None)), |
| 1319 | to: ty.clone(), |
| 1320 | }, |
| 1321 | Type::Duration => Expression::NumberLiteral(0., Unit::Ms), |
| 1322 | Type::Angle => Expression::NumberLiteral(0., Unit::Deg), |
| 1323 | Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx), |
| 1324 | Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px), |
| 1325 | Type::Rem => Expression::NumberLiteral(0., Unit::Rem), |
| 1326 | Type::Percent => Expression::NumberLiteral(100., Unit::Percent), |
| 1327 | Type::Image => Expression::ImageReference { |
| 1328 | resource_ref: ImageReference::None, |
| 1329 | source_location: None, |
| 1330 | nine_slice: None, |
| 1331 | }, |
| 1332 | Type::Bool => Expression::BoolLiteral(false), |
| 1333 | Type::Model => Expression::Invalid, |
| 1334 | Type::PathData => Expression::PathData(Path::Elements(vec![])), |
| 1335 | Type::Array(element_ty) => { |
| 1336 | Expression::Array { element_ty: (**element_ty).clone(), values: vec![] } |
| 1337 | } |
| 1338 | Type::Struct(s) => Expression::Struct { |
| 1339 | ty: s.clone(), |
| 1340 | values: s |
| 1341 | .fields |
| 1342 | .iter() |
| 1343 | .map(|(k, v)| (k.clone(), Expression::default_value_for_type(v))) |
| 1344 | .collect(), |
| 1345 | }, |
| 1346 | Type::Easing => Expression::EasingCurve(EasingCurve::default()), |
| 1347 | Type::Brush => Expression::Cast { |
| 1348 | from: Box::new(Expression::default_value_for_type(&Type::Color)), |
| 1349 | to: Type::Brush, |
| 1350 | }, |
| 1351 | Type::Enumeration(enumeration) => { |
| 1352 | Expression::EnumerationValue(enumeration.clone().default_value()) |
| 1353 | } |
| 1354 | Type::ComponentFactory => Expression::EmptyComponentFactory, |
| 1355 | } |
| 1356 | } |
| 1357 | |
| 1358 | /// Try to mark this expression to a lvalue that can be assigned to. |
| 1359 | /// |
| 1360 | /// Return true if the expression is a "lvalue" that can be used as the left hand side of a `=` or `+=` or similar |
| 1361 | pub fn try_set_rw( |
| 1362 | &mut self, |
| 1363 | ctx: &mut LookupCtx, |
| 1364 | what: &'static str, |
| 1365 | node: &dyn Spanned, |
| 1366 | ) -> bool { |
| 1367 | match self { |
| 1368 | Expression::PropertyReference(nr) => { |
| 1369 | nr.mark_as_set(); |
| 1370 | let mut lookup = nr.element().borrow().lookup_property(nr.name()); |
| 1371 | lookup.is_local_to_component &= ctx.is_local_element(&nr.element()); |
| 1372 | if lookup.property_visibility == PropertyVisibility::Constexpr { |
| 1373 | ctx.diag.push_error( |
| 1374 | "The property must be known at compile time and cannot be changed at runtime" |
| 1375 | .into(), |
| 1376 | node, |
| 1377 | ); |
| 1378 | false |
| 1379 | } else if lookup.is_valid_for_assignment() { |
| 1380 | if !nr |
| 1381 | .element() |
| 1382 | .borrow() |
| 1383 | .property_analysis |
| 1384 | .borrow() |
| 1385 | .get(nr.name()) |
| 1386 | .is_some_and(|d| d.is_linked_to_read_only) |
| 1387 | { |
| 1388 | true |
| 1389 | } else if ctx.is_legacy_component() { |
| 1390 | ctx.diag.push_warning("Modifying a property that is linked to a read-only property is deprecated" .into(), node); |
| 1391 | true |
| 1392 | } else { |
| 1393 | ctx.diag.push_error( |
| 1394 | "Cannot modify a property that is linked to a read-only property" |
| 1395 | .into(), |
| 1396 | node, |
| 1397 | ); |
| 1398 | false |
| 1399 | } |
| 1400 | } else if ctx.is_legacy_component() |
| 1401 | && lookup.property_visibility == PropertyVisibility::Output |
| 1402 | { |
| 1403 | ctx.diag |
| 1404 | .push_warning(format!(" {what} on an output property is deprecated" ), node); |
| 1405 | true |
| 1406 | } else { |
| 1407 | ctx.diag.push_error( |
| 1408 | format!(" {what} on a {} property" , lookup.property_visibility), |
| 1409 | node, |
| 1410 | ); |
| 1411 | false |
| 1412 | } |
| 1413 | } |
| 1414 | Expression::StructFieldAccess { base, .. } => base.try_set_rw(ctx, what, node), |
| 1415 | Expression::RepeaterModelReference { .. } => true, |
| 1416 | Expression::ArrayIndex { array, .. } => array.try_set_rw(ctx, what, node), |
| 1417 | _ => { |
| 1418 | ctx.diag.push_error(format!(" {what} needs to be done on a property" ), node); |
| 1419 | false |
| 1420 | } |
| 1421 | } |
| 1422 | } |
| 1423 | |
| 1424 | /// Unwrap DebugHook expressions to their contained sub-expression |
| 1425 | pub fn ignore_debug_hooks(&self) -> &Expression { |
| 1426 | match self { |
| 1427 | Expression::DebugHook { expression, .. } => expression.as_ref(), |
| 1428 | _ => self, |
| 1429 | } |
| 1430 | } |
| 1431 | } |
| 1432 | |
| 1433 | fn model_inner_type(model: &Expression) -> Type { |
| 1434 | match model { |
| 1435 | Expression::Cast { from: &Box, to: Type::Model } => model_inner_type(model:from), |
| 1436 | Expression::CodeBlock(cb: &Vec) => cb.last().map_or(default:Type::Invalid, f:model_inner_type), |
| 1437 | _ => match model.ty() { |
| 1438 | Type::Float32 | Type::Int32 => Type::Int32, |
| 1439 | Type::Array(elem: Rc) => (*elem).clone(), |
| 1440 | _ => Type::Invalid, |
| 1441 | }, |
| 1442 | } |
| 1443 | } |
| 1444 | |
| 1445 | /// The expression in the Element::binding hash table |
| 1446 | #[derive (Debug, Clone, derive_more::Deref, derive_more::DerefMut)] |
| 1447 | pub struct BindingExpression { |
| 1448 | #[deref] |
| 1449 | #[deref_mut] |
| 1450 | pub expression: Expression, |
| 1451 | /// The location of this expression in the source code |
| 1452 | pub span: Option<SourceLocation>, |
| 1453 | /// How deep is this binding declared in the hierarchy. When two binding are conflicting |
| 1454 | /// for the same priority (because of two way binding), the lower priority wins. |
| 1455 | /// The priority starts at 1, and each level of inlining adds one to the priority. |
| 1456 | /// 0 means the expression was added by some passes and it is not explicit in the source code |
| 1457 | pub priority: i32, |
| 1458 | |
| 1459 | pub animation: Option<PropertyAnimation>, |
| 1460 | |
| 1461 | /// The analysis information. None before it is computed |
| 1462 | pub analysis: Option<BindingAnalysis>, |
| 1463 | |
| 1464 | /// The properties this expression is aliased with using two way bindings |
| 1465 | pub two_way_bindings: Vec<NamedReference>, |
| 1466 | } |
| 1467 | |
| 1468 | impl std::convert::From<Expression> for BindingExpression { |
| 1469 | fn from(expression: Expression) -> Self { |
| 1470 | Self { |
| 1471 | expression, |
| 1472 | span: None, |
| 1473 | priority: 0, |
| 1474 | animation: Default::default(), |
| 1475 | analysis: Default::default(), |
| 1476 | two_way_bindings: Default::default(), |
| 1477 | } |
| 1478 | } |
| 1479 | } |
| 1480 | |
| 1481 | impl BindingExpression { |
| 1482 | pub fn new_uncompiled(node: SyntaxNode) -> Self { |
| 1483 | Self { |
| 1484 | expression: Expression::Uncompiled(node.clone()), |
| 1485 | span: Some(node.to_source_location()), |
| 1486 | priority: 1, |
| 1487 | animation: Default::default(), |
| 1488 | analysis: Default::default(), |
| 1489 | two_way_bindings: Default::default(), |
| 1490 | } |
| 1491 | } |
| 1492 | pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self { |
| 1493 | Self { |
| 1494 | expression, |
| 1495 | span: Some(span), |
| 1496 | priority: 0, |
| 1497 | animation: Default::default(), |
| 1498 | analysis: Default::default(), |
| 1499 | two_way_bindings: Default::default(), |
| 1500 | } |
| 1501 | } |
| 1502 | |
| 1503 | /// Create an expression binding that simply is a two way binding to the other |
| 1504 | pub fn new_two_way(other: NamedReference) -> Self { |
| 1505 | Self { |
| 1506 | expression: Expression::Invalid, |
| 1507 | span: None, |
| 1508 | priority: 0, |
| 1509 | animation: Default::default(), |
| 1510 | analysis: Default::default(), |
| 1511 | two_way_bindings: vec![other], |
| 1512 | } |
| 1513 | } |
| 1514 | |
| 1515 | /// Merge the other into this one. Normally, &self is kept intact (has priority) |
| 1516 | /// unless the expression is invalid, in which case the other one is taken. |
| 1517 | /// |
| 1518 | /// Also the animation is taken if the other don't have one, and the two ways binding |
| 1519 | /// are taken into account. |
| 1520 | /// |
| 1521 | /// Returns true if the other expression was taken |
| 1522 | pub fn merge_with(&mut self, other: &Self) -> bool { |
| 1523 | if self.animation.is_none() { |
| 1524 | self.animation.clone_from(&other.animation); |
| 1525 | } |
| 1526 | let has_binding = self.has_binding(); |
| 1527 | self.two_way_bindings.extend_from_slice(&other.two_way_bindings); |
| 1528 | if !has_binding { |
| 1529 | self.priority = other.priority; |
| 1530 | self.expression = other.expression.clone(); |
| 1531 | true |
| 1532 | } else { |
| 1533 | false |
| 1534 | } |
| 1535 | } |
| 1536 | |
| 1537 | /// returns false if there is no expression or two way binding |
| 1538 | pub fn has_binding(&self) -> bool { |
| 1539 | !matches!(self.expression, Expression::Invalid) || !self.two_way_bindings.is_empty() |
| 1540 | } |
| 1541 | } |
| 1542 | |
| 1543 | impl Spanned for BindingExpression { |
| 1544 | fn span(&self) -> crate::diagnostics::Span { |
| 1545 | self.span.as_ref().map(|x: &SourceLocation| x.span()).unwrap_or_default() |
| 1546 | } |
| 1547 | fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> { |
| 1548 | self.span.as_ref().and_then(|x: &SourceLocation| x.source_file()) |
| 1549 | } |
| 1550 | } |
| 1551 | |
| 1552 | #[derive (Default, Debug, Clone)] |
| 1553 | pub struct BindingAnalysis { |
| 1554 | /// true if that binding is part of a binding loop that already has been reported. |
| 1555 | pub is_in_binding_loop: Cell<bool>, |
| 1556 | |
| 1557 | /// true if the binding is a constant value that can be set without creating a binding at runtime |
| 1558 | pub is_const: bool, |
| 1559 | |
| 1560 | /// true if this binding does not depends on the value of property that are set externally. |
| 1561 | /// When true, this binding cannot be part of a binding loop involving external components |
| 1562 | pub no_external_dependencies: bool, |
| 1563 | } |
| 1564 | |
| 1565 | #[derive (Debug, Clone)] |
| 1566 | pub enum Path { |
| 1567 | Elements(Vec<PathElement>), |
| 1568 | Events(Vec<Expression>, Vec<Expression>), |
| 1569 | Commands(Box<Expression>), // expr must evaluate to string |
| 1570 | } |
| 1571 | |
| 1572 | #[derive (Debug, Clone)] |
| 1573 | pub struct PathElement { |
| 1574 | pub element_type: Rc<BuiltinElement>, |
| 1575 | pub bindings: BindingsMap, |
| 1576 | } |
| 1577 | |
| 1578 | #[derive (Clone, Debug, Default)] |
| 1579 | pub enum EasingCurve { |
| 1580 | #[default] |
| 1581 | Linear, |
| 1582 | CubicBezier(f32, f32, f32, f32), |
| 1583 | EaseInElastic, |
| 1584 | EaseOutElastic, |
| 1585 | EaseInOutElastic, |
| 1586 | EaseInBounce, |
| 1587 | EaseOutBounce, |
| 1588 | EaseInOutBounce, |
| 1589 | // CubicBezierNonConst([Box<Expression>; 4]), |
| 1590 | // Custom(Box<dyn Fn(f32)->f32>), |
| 1591 | } |
| 1592 | |
| 1593 | // The compiler generates ResourceReference::AbsolutePath for all references like @image-url("foo.png") |
| 1594 | // and the resource lowering path may change this to EmbeddedData if configured. |
| 1595 | #[derive (Clone, Debug)] |
| 1596 | pub enum ImageReference { |
| 1597 | None, |
| 1598 | AbsolutePath(SmolStr), |
| 1599 | EmbeddedData { resource_id: usize, extension: String }, |
| 1600 | EmbeddedTexture { resource_id: usize }, |
| 1601 | } |
| 1602 | |
| 1603 | /// Print the expression as a .slint code (not necessarily valid .slint) |
| 1604 | pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result { |
| 1605 | match expression { |
| 1606 | Expression::Invalid => write!(f, "<invalid>" ), |
| 1607 | Expression::Uncompiled(u) => write!(f, " {u:?}" ), |
| 1608 | Expression::StringLiteral(s) => write!(f, " {s:?}" ), |
| 1609 | Expression::NumberLiteral(vl, unit) => write!(f, " {vl}{unit}" ), |
| 1610 | Expression::BoolLiteral(b) => write!(f, " {b:?}" ), |
| 1611 | Expression::PropertyReference(a) => write!(f, " {a:?}" ), |
| 1612 | Expression::ElementReference(a) => write!(f, " {a:?}" ), |
| 1613 | Expression::RepeaterIndexReference { element } => { |
| 1614 | crate::namedreference::pretty_print_element_ref(f, element) |
| 1615 | } |
| 1616 | Expression::RepeaterModelReference { element } => { |
| 1617 | crate::namedreference::pretty_print_element_ref(f, element)?; |
| 1618 | write!(f, ".@model" ) |
| 1619 | } |
| 1620 | Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_ {index}" ), |
| 1621 | Expression::StoreLocalVariable { name, value } => { |
| 1622 | write!(f, " {name} = " )?; |
| 1623 | pretty_print(f, value) |
| 1624 | } |
| 1625 | Expression::ReadLocalVariable { name, ty: _ } => write!(f, " {name}" ), |
| 1626 | Expression::StructFieldAccess { base, name } => { |
| 1627 | pretty_print(f, base)?; |
| 1628 | write!(f, ". {name}" ) |
| 1629 | } |
| 1630 | Expression::ArrayIndex { array, index } => { |
| 1631 | pretty_print(f, array)?; |
| 1632 | write!(f, "[" )?; |
| 1633 | pretty_print(f, index)?; |
| 1634 | write!(f, "]" ) |
| 1635 | } |
| 1636 | Expression::Cast { from, to } => { |
| 1637 | write!(f, "(" )?; |
| 1638 | pretty_print(f, from)?; |
| 1639 | write!(f, "/* as {to} */)" ) |
| 1640 | } |
| 1641 | Expression::CodeBlock(c) => { |
| 1642 | write!(f, " {{ " )?; |
| 1643 | for e in c { |
| 1644 | pretty_print(f, e)?; |
| 1645 | write!(f, "; " )?; |
| 1646 | } |
| 1647 | write!(f, " }}" ) |
| 1648 | } |
| 1649 | Expression::FunctionCall { function, arguments, source_location: _ } => { |
| 1650 | match function { |
| 1651 | Callable::Builtin(b) => write!(f, " {b:?}" )?, |
| 1652 | Callable::Callback(nr) | Callable::Function(nr) => write!(f, " {nr:?}" )?, |
| 1653 | } |
| 1654 | write!(f, "(" )?; |
| 1655 | for e in arguments { |
| 1656 | pretty_print(f, e)?; |
| 1657 | write!(f, ", " )?; |
| 1658 | } |
| 1659 | write!(f, ")" ) |
| 1660 | } |
| 1661 | Expression::SelfAssignment { lhs, rhs, op, .. } => { |
| 1662 | pretty_print(f, lhs)?; |
| 1663 | write!(f, " {}= " , if *op == '=' { ' ' } else { *op })?; |
| 1664 | pretty_print(f, rhs) |
| 1665 | } |
| 1666 | Expression::BinaryExpression { lhs, rhs, op } => { |
| 1667 | write!(f, "(" )?; |
| 1668 | pretty_print(f, lhs)?; |
| 1669 | match *op { |
| 1670 | '=' | '!' => write!(f, " {op}= " )?, |
| 1671 | _ => write!(f, " {op} " )?, |
| 1672 | }; |
| 1673 | pretty_print(f, rhs)?; |
| 1674 | write!(f, ")" ) |
| 1675 | } |
| 1676 | Expression::UnaryOp { sub, op } => { |
| 1677 | write!(f, " {op}" )?; |
| 1678 | pretty_print(f, sub) |
| 1679 | } |
| 1680 | Expression::ImageReference { resource_ref, .. } => write!(f, " {resource_ref:?}" ), |
| 1681 | Expression::Condition { condition, true_expr, false_expr } => { |
| 1682 | write!(f, "if (" )?; |
| 1683 | pretty_print(f, condition)?; |
| 1684 | write!(f, ") {{ " )?; |
| 1685 | pretty_print(f, true_expr)?; |
| 1686 | write!(f, " }} else {{ " )?; |
| 1687 | pretty_print(f, false_expr)?; |
| 1688 | write!(f, " }}" ) |
| 1689 | } |
| 1690 | Expression::Array { element_ty: _, values } => { |
| 1691 | write!(f, "[" )?; |
| 1692 | for e in values { |
| 1693 | pretty_print(f, e)?; |
| 1694 | write!(f, ", " )?; |
| 1695 | } |
| 1696 | write!(f, "]" ) |
| 1697 | } |
| 1698 | Expression::Struct { ty: _, values } => { |
| 1699 | write!(f, " {{ " )?; |
| 1700 | for (name, e) in values { |
| 1701 | write!(f, " {name}: " )?; |
| 1702 | pretty_print(f, e)?; |
| 1703 | write!(f, ", " )?; |
| 1704 | } |
| 1705 | write!(f, " }}" ) |
| 1706 | } |
| 1707 | Expression::PathData(data) => write!(f, " {data:?}" ), |
| 1708 | Expression::EasingCurve(e) => write!(f, " {e:?}" ), |
| 1709 | Expression::LinearGradient { angle, stops } => { |
| 1710 | write!(f, "@linear-gradient(" )?; |
| 1711 | pretty_print(f, angle)?; |
| 1712 | for (c, s) in stops { |
| 1713 | write!(f, ", " )?; |
| 1714 | pretty_print(f, c)?; |
| 1715 | write!(f, " " )?; |
| 1716 | pretty_print(f, s)?; |
| 1717 | } |
| 1718 | write!(f, ")" ) |
| 1719 | } |
| 1720 | Expression::RadialGradient { stops } => { |
| 1721 | write!(f, "@radial-gradient(circle" )?; |
| 1722 | for (c, s) in stops { |
| 1723 | write!(f, ", " )?; |
| 1724 | pretty_print(f, c)?; |
| 1725 | write!(f, " " )?; |
| 1726 | pretty_print(f, s)?; |
| 1727 | } |
| 1728 | write!(f, ")" ) |
| 1729 | } |
| 1730 | Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value) { |
| 1731 | Some(val) => write!(f, " {}. {}" , e.enumeration.name, val), |
| 1732 | None => write!(f, " {}. {}" , e.enumeration.name, e.value), |
| 1733 | }, |
| 1734 | Expression::ReturnStatement(e) => { |
| 1735 | write!(f, "return " )?; |
| 1736 | e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(())) |
| 1737 | } |
| 1738 | Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { |
| 1739 | write!( |
| 1740 | f, |
| 1741 | " {:?}[ {}{}]" , |
| 1742 | layout_cache_prop, |
| 1743 | index, |
| 1744 | if repeater_index.is_some() { " + $index" } else { "" } |
| 1745 | ) |
| 1746 | } |
| 1747 | Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)" ), |
| 1748 | Expression::SolveLayout(..) => write!(f, "solve_layout(..)" ), |
| 1749 | Expression::MinMax { ty: _, op, lhs, rhs } => { |
| 1750 | match op { |
| 1751 | MinMaxOp::Min => write!(f, "min(" )?, |
| 1752 | MinMaxOp::Max => write!(f, "max(" )?, |
| 1753 | } |
| 1754 | pretty_print(f, lhs)?; |
| 1755 | write!(f, ", " )?; |
| 1756 | pretty_print(f, rhs)?; |
| 1757 | write!(f, ")" ) |
| 1758 | } |
| 1759 | Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>" ), |
| 1760 | Expression::DebugHook { expression, id } => { |
| 1761 | write!(f, "debug-hook(" )?; |
| 1762 | pretty_print(f, expression)?; |
| 1763 | write!(f, " \"{id}\")" ) |
| 1764 | } |
| 1765 | } |
| 1766 | } |
| 1767 | |